Thread: Passing by partly-constant reference?

Hybrid View

Previous Post Previous Post   Next Post Next Post
  1. #1
    Registered User
    Join Date
    Jul 2014
    Location
    Calgary, Canada
    Posts
    29

    Passing by partly-constant reference?

    Evening all,


    I am open to suggestions with this problem.


    I have a class setup that resembles the following:


    Code:
    #include <vector>
    #include <numeric> // std::iota
    #include <cstdio>  // just 'cause I loves printf()
    
    
    
    
    class Base
    {
     protected:
    
      using Object = std::vector<int>;
    
     private:
    
      Object embedded;
    
     public:
    
      Base(int i)
      {
        embedded.resize(i);
    
        std::iota(embedded.begin(), embedded.end(), 0);
      }
    
     protected:
    
      void          replace(Object const& x) { embedded = x; }
    
      void          resize(int i)            { embedded.resize(i); }
    
     public:
    
      int         * data()                   { return embedded.data(); }
      
      int    const* data()         const     { return embedded.data(); }
    
      Object const& get_embedded() const     { return embedded; }
    };
    
    
    void NonMemberFunction(std::vector<int>& eee)
    {
      if (eee.size() > 0)
      {
        eee.data()[0] = 103;
      }
    }
    
    
    class Derived : public Base
    {
     public:
    
      Derived(int i) : Base(i) { }
    
      void MemberFunction()
      {
        Object temp(get_embedded());
    
        NonMemberFunction(temp);
    
        replace(temp);
      }
    };
    
    
    int main()
    {
      Derived foo(7);
    
      foo.data()[1] = 36;
      foo.data()[5] = 49;
    
      foo.MemberFunction();
    
      for (int i=0; i<foo.get_embedded().size(); ++i)
        printf("%d %d\n", i, foo.data()[i]);
    }

    So, briefly:


    (1) Base has an embedded container-type Object.


    (2) Anybody can access the attributes of the Object via get_embedded().


    (3) Anybody can mutate the entries of the Object -- that's perfectly fine, but:


    (4) Only Base and its Derived classes should be able to resize the embedded Object. (This is important.)


    The MemberFunction of the Derived class shows the problem. Since the NonMemberFunction doesn't attempt to resize its argument, one would think there should be some way of passing the embedded Object straight from Derived::MemberFunction to the NonMemberFunction, via the latter's argument list. But of course point (4) demands that the embedded Object be passed by constant reference only. So I've fallen back on passing a local copy back and forth, which is a lousy kludge, and time consuming too.


    Sure, I could let get_embedded() return by non-constant reference -- and bust Base's encapsulation. Badly.


    In a nutshell, I need three levels of constness here.


    In the actual code, Object is one of my own classes, not an std::vector. My first thought was to find some way of passing the Object as a FixedSizeObject&. The problem there of course is that FixedSizeObject would then have to be the base class of Object, and it doesn't really make sense for the base class to have immutable dimensions, while the derived class can be resized.


    I could of course just send the data() pointer into NonMemberFunction as an argument, along with whatever other attributes of Object might be needed. Or perhaps an Object::Iterator would solve the problem?... Anyway, not being a guru, I wanted to see what you guys thought.


    Thanks
    Grump
    Last edited by Grumpulus; 06-19-2015 at 10:22 PM. Reason: Fixed formatting problems

  2. #2
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    Since the NonMemberFunction doesn't attempt to resize its argument, one would think there should be some way of passing the embedded Object straight from Derived::MemberFunction to the NonMemberFunction, via the latter's argument list.
    O_o

    You really can't blacklist public interfaces in the C++ language from being used. You can only pretend to blacklist public interfaces by dancing around a lot of techniques. The fact that no real way to blacklist public interfaces exists means that calling `NonMemberFunction` with the internal object is flawed because you can't actually know that `NonMemberFunction`, in general not the specific instance, doesn't use the interfaces the owner wouldn't want to be used.

    That said, you could pretend to blacklist the relevant methods by implementing the proxy class `ObjectNoResize` in terms of `ObjectInterface` which overloads `resize` to always throw an exception. Of course, you now aren't trapping the misuse of the blacklisted member until program execution when you could be trapping the misuse during compilation by whitelisting only the methods required for the mechanisms being implemented.

    I'd suggest you just whitelist only the interfaces you want to expose.

    The problem there of course is that FixedSizeObject would then have to be the base class of Object, and it doesn't really make sense for the base class to have immutable dimensions, while the derived class can be resized.
    The problem is that you are calling the parent class by the `FixedSizeObject` label thus lying in the interface.

    You might think in terms of `ObjectResizable` which can be constrained as a `Object`, if you expose only the right interfaces, without lying.

    Or perhaps an Object::Iterator would solve the problem?
    I'd recommend using iterators anyway for what you seem to be doing, but I like iterators.

    But of course point (4) demands that the embedded Object be passed by constant reference only. So I've fallen back on passing a local copy back and forth, which is a lousy kludge, and time consuming too.
    I actually came to address the "kludge" comment.

    The `MemberFunction` is a public method meaning anyone can use the method.

    The `NonMemberFunction` utility used by `MemberFunction` doesn't throw an exception, but what if `NonMemberFunction` did throw an exception?

    The current implementation of `MemberFunction` makes any exception thrown by `NonMemberFunction` irrelevant because the internal state isn't modified until `MemberFunction` successfully completes. The `MemberFunction` implementation then makes the strong exception guarantee. If you were to directly provide access to the internal state, the `MemberFunction` implementation would loose the strong guarantee. A primitive which implements the strong guarantee is extremely useful because clients using the interface can thus implement the strong guarantee without wrapping the used interfaces in otherwise pointless boilerplate.

    In other words, because you are implementing the strong guarantee I as client do not have to write my code to prevent mutating the state in the face of an exception. I don't have to do this:

    Code:
    void MyFunction(const Derived & f, /* ... */)
    {
        Derived sCopy(f);
        // use sCopy
        f = sCopy;
    }
    The usefulness of making a copy isn't just limited to the potential of exceptions. What if `NonMemberFunction` was written by a third-party which eventually did change the size of the collection? By making a copy, you can enforce the statement "The `MemberFunction` function doesn't change the size of the collection." by throwing an exception if the size is unexpectedly different instead of replacing the internal element.

    *sigh*

    Now, we can talk about performance. I, again, am all for making the copy. We don't have to make the copy twice to still hit all the important notes.

    We are looking at copying an `Object`, and we are responsible for implementing the `Object` class.

    What is the `Object` class? Maybe a pointer to `int` for the data and a `int` for the size? Okay. We need to implement `swap` which will swap, obviously, the bits of two `Object` instances such that the exchange can't throw an exception. Because we only have a pointer and an integer, we can manage the exception guarantee with ease.

    Code:
    void Object::swap
    (
        Object & fOther
    )
    {
        using std::swap; // important
        swap(this->mDataPointer, fOther.mDataPointer);
        swap(this->mDataSize, fOther.mDataSize);
    }
    We need to implement `Base::replace` in terms of the new method.

    Code:
    void Base::replace
    (
        Object & f
    )
    {
        embedded.swap(f);
    }
    The implementation for `MemberFunction` hasn't changed, but we have reduced the cost.

    We could do something similar even if we didn't control the implementation for the `Object` class.

    Now, you will notice that `NonMemberFunction` has some preconditions which are checked before anything is done with the argument. Unfortunately, we will copy the `Object` instance even if the preconditions aren't matched. We can avoid the copy by implementing the preconditions as a state of the `NonMemberFunction` function allowing the implementation for `MemberFunction` to check if `NonMemberFunction` would do anything before making the copy.

    *shrug*

    I do not see making the copy before passing the state to an external function as a kludge. I see making the copy as being a part of a correct and robust implementation. You'll be able to enforce any relevant guarantees of the `Derived` class interface before any of the operations might have taken place.

    Soma
    “Salem Was Wrong!” -- Pedant Necromancer
    “Four isn't random!” -- Gibbering Mouther

  3. #3
    Registered User
    Join Date
    Jul 2014
    Location
    Calgary, Canada
    Posts
    29
    Hi Soma,


    Thanks very much for your thoughts on this.


    You really can't blacklist public interfaces in the C++ language from being used.

    Yeah, that is the real issue here. I was trying to find some way of telling the compiler "Make this public, but only when it's safe for it to be public".


    The problem is that you are calling the parent class by the `FixedSizeObject` label thus lying in the interface.

    Good point. I think I see what you mean -- to have ResizableObject (as you call it) inherit from something called FixedSizeObject implies that a ResizableObject "is-a" FixedSizeObject. That's really counterintuitive.


    I'd recommend using iterators anyway for what you seem to be doing, but I like iterators.

    I am still learning how iterators work. That is the main reason why Object doesn't have an ObjectIterator class yet.


    It will have one, though. The usefulness of iterators is becoming more apparent to me these days, ever since I discovered the <algorithm> library a few months back.


    The current implementation of `MemberFunction` makes any exception thrown by `NonMemberFunction` irrelevant because the internal state isn't modified until `MemberFunction` successfully completes. The `MemberFunction` implementation then makes the strong exception guarantee.

    That is a really good point. I hadn't even considered exception safety here. I was looking at this entirely from a performance standpoint.


    That standpoint comes sort of naturally, I guess. It can take days to run some of our jobs, so performance is the main issue. (Also, nobody in our industry has ever heard of exception safety, so managers don't care about it, unfortunately.)


    What is the `Object` class? Maybe a pointer to `int` for the data and a `int` for the size?

    You're almost bang-on. It's a two-dimensional array template, with two integers for the dimensions, a third integer for the allocated capacity, and a pointer-to-Type. (Sadly, I didn't know about the various publicly available matrix types like Boost::Multi_Array when I started writing it.)


    Code:
    void Object::swap
    (
        Object & fOther
    )
    {
        using std::swap; // important
        swap(this->mDataPointer, fOther.mDataPointer);
        swap(this->mDataSize, fOther.mDataSize);
    }

    I have to be honest with you: I just can't believe that I didn't think of this myself. Object doesn't have a swap() yet, but it does have a move constructor. Writing a swap() should have been the logical next step after that.


    This is almost certainly the way I will go. It should reduce the current copying time pretty dramatically, and, as you pointed out, give me better exception safety on top of that.


    By the way -- there's no problem with putting that conditional in Derived::MemberFunction instead of NonMemberFunction. In fact it is probably coded that way already in the real Object class.


    Cheers and thanks again,
    Grump

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. function that returns a constant reference
    By gunitinug in forum C++ Programming
    Replies: 2
    Last Post: 06-30-2012, 06:27 AM
  2. Partly shared memory
    By DrSnuggles in forum C++ Programming
    Replies: 13
    Last Post: 01-21-2009, 03:35 AM
  3. Pass by constant reference
    By Canadian0469 in forum C++ Programming
    Replies: 4
    Last Post: 11-15-2008, 03:25 PM
  4. Assigning an object variable to a constant reference
    By Canadian0469 in forum C++ Programming
    Replies: 18
    Last Post: 11-10-2008, 10:48 AM
  5. Replies: 9
    Last Post: 01-29-2006, 06:57 PM