Thread: Choosing between pointer or reference_wrapper for member variable

  1. #1
    C++ rookie Galdor's Avatar
    Join Date
    Apr 2016
    Location
    Belgium
    Posts
    32

    Choosing between pointer or reference_wrapper for member variable

    Hello to all,

    Let FieldObj_A be an object of type Field that is too large for a copy to be cheap/affordable.
    Let MyClass be a class that must access to the data held by FieldObj_A.

    I can either :
    1. Copy FieldObj_A as a class member.
      This is a bad solution since FieldObj is too large.
    2. Store a reference to this object.
      This is also a bad solution (in my case) because the copy-assignment operator won't be able to copy it…
    3. Store a pointer to this object.
      Works, but the synthax is not very nice to read. I.e. any time I want to access content of pointer _FieldObj, I need to write a star '*' before it : "*_FieldObj".
    4. Define a reference_wrapper that refers to this object.
      Works, but all the examples I have found so far on the internet present it as a way to make a vector of references to objects. Therefore I am not sure whether it is suitable here ?


    What do you think is the best implementation ?
    In which cases would you define a member variable as a reference to object/variable, eventhough it (i.e. the member variable) cannot be copied ?


    Here is a dummy example. For the sake of simplicity, type Field is defined here as a synonym for type double. In the real case Field is a class wrapping an array/vector.

    Code:
    #include <functional>
    #include <iostream>
    
    typedef double Field;
    
    ///////////////////////////////////////////////////////////
    class MyClass
    ///////////////////////////////////////////////////////////
    {
        //
    public:
        //
    //  MyClass(Field & FieldObj) : _FieldObj(FieldObj ) {}; //Option #1 : store a copy              of the object
    //  MyClass(Field & FieldObj) : _FieldObj(&FieldObj) {}; //Option #2 : store a pointer           to the object
    //  MyClass(Field & FieldObj) : _FieldObj(FieldObj ) {}; //Option #3 : store a reference         to the object
        MyClass(Field & FieldObj) : _FieldObj(FieldObj ) {}; //Option #4 : store a reference_wrapper to the object
        //
        /*****/   ~MyClass  (void)                   = default; //Destructor
        /*****/   MyClass   (const MyClass &  other) = default; //Copy constructor
        /*****/   MyClass   (/***/ MyClass && other) = default; //Move constructor
        MyClass & operator= (const MyClass &  other) = default; //Copy-assignment operator
        MyClass & operator= (/***/ MyClass && other) = default; //Move-assignment operator
        //
    //  Field                          _FieldObj; //Option #1 : store a copy              of the object
    //  Field                        * _FieldObj; //Option #2 : store a pointer           to the object
    //  Field                        & _FieldObj; //Option #3 : store a reference         to the object
        std::reference_wrapper<Field>  _FieldObj; //Option #4 : store a reference_wrapper to the object
    };
    
    ///////////////////////////////////////////////////////////
    
    int main(void)
    {
        //
        // VARIABLE INITIALISATION
        //
        Field FieldObj_A = 3.0;
        Field FieldObj_B = 5.0;
        //
        MyClass ObjFirst( FieldObj_A );
        MyClass ObjSecnd( FieldObj_B );
        //
        std::cout << "FieldObj_A (" << &FieldObj_A << ") = " << FieldObj_A << std::endl;
        std::cout << "FieldObj_B (" << &FieldObj_B << ") = " << FieldObj_B << std::endl;
        //
        // MAIN
        //
        std::cout << ObjFirst._FieldObj << std::endl;
        std::cout << ObjSecnd._FieldObj << std::endl;
        //
        ObjSecnd = ObjFirst; //Fails for option #3 because copy assignment operator is deleted
        //-------------------//(because class has no copiable member variables)
        //
        std::cout << ObjFirst._FieldObj << std::endl;
        std::cout << ObjSecnd._FieldObj << std::endl;
        //
        return 0;
    }
    Thanks in advance !

  2. #2
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by Galdor
    Let FieldObj_A be an object of type Field that is too large for a copy to be cheap/affordable.
    Let MyClass be a class that must access to the data held by FieldObj_A.
    (...)
    Store a reference to this object.
    This is also a bad solution (in my case) because the copy-assignment operator won't be able to copy it…
    But if copying is expensive such that you would consider a reference member to avoid copying, then it follows that you would want to disable copy assignment, maybe even disable copy construction. Having a pointer to FieldObj_A or using reference_wrapper would not solve the problem since the copying would only be shallow copying, and if you changed it to deep copying then the "too large for a copy to be cheap" problem comes into play.

    Maybe the solution is to simply accept that because copying is expensive, you will disable it by disabling copy construction and copy assignment. Hopefully you can implement cheap move construction and move assignment, in which case option #1 would be fine.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  3. #3
    C++ rookie Galdor's Avatar
    Join Date
    Apr 2016
    Location
    Belgium
    Posts
    32
    Thanks for your answer !

    Quote Originally Posted by laserlight View Post
    Maybe the solution is to simply accept that because copying is expensive, you will disable it by disabling copy construction and copy assignment. Hopefully you can implement cheap move construction and move assignment, in which case option #1 would be fine.
    The data within Field objects is used by several objects, MyClass objects being some of these.

    MyClass (as well as some other classes) basically perform computations on Field objects. They use the data within some Field objects as input and alter the content of other Field objects as output. MyClass is not a "container" class.

    Quote Originally Posted by laserlight View Post
    Having a pointer to FieldObj_A or using reference_wrapper would not solve the problem since the copying would only be shallow copying, and if you changed it to deep copying then the "too large for a copy to be cheap" problem comes into play.
    Actually, I intended shallow copying. My idea was to store the data into a Field object and to have several objects, including MyClass objects, reading into that same Field object directly. So shallow copy is fine.

    Quote Originally Posted by laserlight View Post
    But if copying is expensive such that you would consider a reference member to avoid copying, then it follows that you would want to disable copy assignment, maybe even disable copy construction.
    I haven't thought of deleting the copy-assignment operator… That might be the smartest thing to do in my case ! Indeed, it does not make much sense to copy-assign a class whose purpose is to compute things.

    Questions : Do you define reference members in a class only if you know that you won't ever need to copy-assign an instance of this class ? Otherwise what do you use ? Pointers ? Do you sometimes use reference_wrapper for similar purposes ?

  4. #4
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by Galdor
    Actually, I intended shallow copying. My idea was to store the data into a Field object and to have several objects, including MyClass objects, reading into that same Field object directly. So shallow copy is fine.
    It sounds like you should be storing a shared_ptr then. You wouldn't need to worry about copying MyClass objects since the Field member will be reference counted by the shared_ptr.

    EDIT:
    But wait, I see:
    Quote Originally Posted by Galdor
    MyClass (as well as some other classes) basically perform computations on Field objects. They use the data within some Field objects as input and alter the content of other Field objects as output.
    Why do you need to store them then? Wouldn't it be possible to have them as in/out reference parameters?
    Last edited by laserlight; 05-30-2016 at 10:35 AM.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  5. #5
    C++ rookie Galdor's Avatar
    Join Date
    Apr 2016
    Location
    Belgium
    Posts
    32
    Quote Originally Posted by laserlight View Post
    It sounds like you should be storing a shared_ptr then. You wouldn't need to worry about copying MyClass objects since the Field member will be reference counted by the shared_ptr.
    I thought shared_ptr was meant to be used only when data was dynamically allocated, and therefore has to be deallocated at some point ? Here the Field objects are in the stack, although the data they contain (i.e. the data they "point to") has been dynamically allocated. Moreover, the Field objects should continue to exist even if no "computation" objects are using them anymore.

    Quote Originally Posted by laserlight View Post
    Why do you need to store them then? Wouldn't it be possible to have them as in/out reference parameters?
    Not always…
    Sometimes the constructor requires the Field objects in order to initialise other members of the class. Then an operator() member function still needs to use the same Field objects. So either
    • The operator() member has no parameters and reuses the same Field objects as the constructor
    • or the operator() member takes the Field objects as input. However in that case it is important to my application that the Field objects used by the operator() method are exactly the same as the Field objects that were passed to the constructor.


    So in any case I need to store a reference to those Field objects in a member variable.

  6. #6
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by Galdor
    I thought shared_ptr was meant to be used only when data was dynamically allocated, and therefore has to be deallocated at some point ?
    Yes.

    Quote Originally Posted by Galdor
    Here the Field objects are in the stack, although the data they contain (i.e. the data they "point to") has been dynamically allocated. Moreover, the Field objects should continue to exist even if no "computation" objects are using them anymore.
    Ah, then you have no worry about lifetime as long as your Field objects are created before the various other objects.

    Going back to your previous set of questions:
    Quote Originally Posted by Galdor
    Do you define reference members in a class only if you know that you won't ever need to copy-assign an instance of this class ?
    Yes, since they effectively make the class non-copy assignable under normal semantics since references cannot be rebound. You would also be unable to move assign under normal semantics.

    Quote Originally Posted by Galdor
    Otherwise what do you use ? Pointers ? Do you sometimes use reference_wrapper for similar purposes ?
    It is usually best to just have ordinary objects as members, otherwise smart pointers. In your case though, you are necessarily referring to an object that exists elsewhere and which the class does not own. If the object could be optional, I would reach for an ordinary pointer. If not, maybe reference_wrapper would be a good idea, but you should be aware that just choosing it to avoid pointer syntax is not a good idea: there are pitfalls in its use too (e.g., the reference lifetime, though that's not applicable here, and then needing to call get in some situations), but they just don't involve the pitfall of dereferencing a null pointer or invalid pointer or doing incorrect pointer arithmetic (but then none of these might be applicable here too).
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  7. #7
    C++ rookie Galdor's Avatar
    Join Date
    Apr 2016
    Location
    Belgium
    Posts
    32
    Quote Originally Posted by laserlight View Post
    Yes, since they effectively make the class non-copy assignable under normal semantics since references cannot be rebound. You would also be unable to move assign under normal semantics.
    What do you mean by "normal semantics" ?

    Quote Originally Posted by laserlight View Post
    It is usually best to just have ordinary objects as members, otherwise smart pointers. In your case though, you are necessarily referring to an object that exists elsewhere and which the class does not own. If the object could be optional, I would reach for an ordinary pointer. If not, maybe reference_wrapper would be a good idea, but you should be aware that just choosing it to avoid pointer syntax is not a good idea: there are pitfalls in its use too (e.g., the reference lifetime, though that's not applicable here, and then needing to call get in some situations), but they just don't involve the pitfall of dereferencing a null pointer or invalid pointer or doing incorrect pointer arithmetic (but then none of these might be applicable here too).
    And what about references ? Would it be your third choice after pointers and reference_wrapper ?

  8. #8
    C++ rookie Galdor's Avatar
    Join Date
    Apr 2016
    Location
    Belgium
    Posts
    32
    Here is a new version of the dummy code :

    Code:
    #include <functional>
    #include <iostream>
     
    typedef double Field;
     
    ///////////////////////////////////////////////////////////
    class MyClass
    ///////////////////////////////////////////////////////////
    {
        //
    public:
        //
    //  MyClass(Field & FieldObj) : _FieldObj(FieldObj ) {}; //Option #1 : store a copy              of the object
    //  MyClass(Field & FieldObj) : _FieldObj(&FieldObj) {}; //Option #2 : store a pointer           to the object
        MyClass(Field & FieldObj) : _FieldObj(FieldObj ) {}; //Option #3 : store a reference         to the object
    //  MyClass(Field & FieldObj) : _FieldObj(FieldObj ) {}; //Option #4 : store a reference_wrapper to the object
        //
        /*****/   ~MyClass  (void)                   = default; //Destructor
        /*****/   MyClass   (const MyClass &  other) = default; //Copy constructor
        /*****/   MyClass   (/***/ MyClass && other) = default; //Move constructor
    //  MyClass & operator= (const MyClass &  other) = default; //Options #1, #2 & #3 : Copy-assignment operator
        MyClass & operator= (const MyClass &  other) = delete;  //Option #3           : No copy-assignment operator
    //  MyClass & operator= (/***/ MyClass && other) = default; //Options #1, #2 & #3 : Move-assignment operator
        MyClass & operator= (/***/ MyClass && other) = delete;  //Option #3           : No move-assignment operator
        //
    //  Field                          _FieldObj; //Option #1 : store a copy              of the object
    //  Field                        * _FieldObj; //Option #2 : store a pointer           to the object
        Field                        & _FieldObj; //Option #3 : store a reference         to the object
    //  std::reference_wrapper<Field>  _FieldObj; //Option #4 : store a reference_wrapper to the object
    };
     
    ///////////////////////////////////////////////////////////
     
    int main(void)
    {
        //
        // VARIABLE INITIALISATION
        //
        Field FieldObj_A = 3.0;
        Field FieldObj_B = 5.0;
        //
        MyClass ObjFirst( FieldObj_A );
        MyClass ObjSecnd( FieldObj_B );
        //
        std::cout << "Results"          << std::endl;
        //
        std::cout << "\tFieldObj_A (" << &FieldObj_A << ") = " << FieldObj_A << std::endl;
        std::cout << "\tFieldObj_B (" << &FieldObj_B << ") = " << FieldObj_B << std::endl;
        //
        std::cout << "\t" << ObjFirst._FieldObj << std::endl;
        std::cout << "\t" << ObjSecnd._FieldObj << std::endl;
        //
        std::cout << std::endl;
        //
        // CALL TO COPY CONSTRUCTOR & COPY ASSIGNMENT OPERATOR
        //
        MyClass ObjThird(ObjFirst);
    //  ObjSecnd = ObjFirst; //Fails for option #3 because copy assignment operator is deleted
    //  //-------------------//(because class has no copiable member variables)
        //
        std::cout << "After call to copy constructor and copy-ass. oper."  << std::endl;
        std::cout << "\t" << "ObjFirst._FieldObj = " << ObjFirst._FieldObj << std::endl;
        std::cout << "\t" << "ObjSecnd._FieldObj = " << ObjSecnd._FieldObj << std::endl;
        std::cout << "\t" << "ObjThird._FieldObj = " << ObjThird._FieldObj << std::endl;
        std::cout << std::endl;
        //
        // CALL TO MOVE CONSTRUCTOR
        //
        MyClass ObjFouth = MyClass(FieldObj_B);
        //
        std::cout << "After call to move constructor"                      << std::endl;
        std::cout << "\t" << "ObjFirst._FieldObj = " << ObjFirst._FieldObj << std::endl;
        std::cout << "\t" << "ObjFouth._FieldObj = " << ObjFouth._FieldObj << std::endl;
        std::cout << std::endl;
        //
        // CALL TO MOVE-ASSIGNMENT OPERATOR
        //
    //  ObjFouth = std::move(ObjFirst); //Fails for option #3 because move-assignment is implicitely deleted
        //------------------------------//(because attribute _FieldObj cannot be overwritten)
        //
        std::cout << "After call to move-assignment operator"              << std::endl;
        std::cout << "\t" << "ObjFirst._FieldObj = " << ObjFirst._FieldObj << std::endl;
        std::cout << "\t" << "ObjFouth._FieldObj = " << ObjFouth._FieldObj << std::endl;
        std::cout << std::endl;
        //
        return 0;
    }

  9. #9
    C++ rookie Galdor's Avatar
    Join Date
    Apr 2016
    Location
    Belgium
    Posts
    32
    I just realised that I have an issue with classes that can't be copy-assigned or move-assigned.

    Suppose that I have a class named BroaderClass whose purpose is to perform several computations. It owns (amongst other things) a MyClass object named MyObj which is in charge of performing a part of the computations. If MyObj cannot be copy-assigned or move-assigned then it must be initialised in the initialisation list…

    Alternatively, if it is absent from the initialiser list then the default constructor will be called and MyObj will have to be "overwritten" using copy- or move-assignment operator within the constructor's definition. For instance :

    Code:
    BroaderClass( [...] )
        : [...] //No initialisation of MyObj here : default constructor will be called instead
    {
        MyObj = MyClass( [...] ); //"Overwrite" object using move assignment operator
    }
    However, if I initialise all the objects in the initialiser list, I end up with initialising lists that can be several hundreds lines long. Is this ok ?

    For instance, my initialisation lists look something like this : (actual code)

    Code:
    Ell_XEqGradR_and_LaplREqDivA_per_dft_fftw::                                                                           
    Ell_XEqGradR_and_LaplREqDivA_per_dft_fftw                                                                             
            (/***/ DblArray & Xx                                                                                          
            ,/***/ DblArray & Xy                                                                                          
            ,/***/ DblArray & Ax                                                                                          
            ,/***/ DblArray & Ay                                                                                          
            )     
        :_lX       ( Xx._D.lX )                                                                                           
        ,_lY       ( Xx._D.lY )                                                                                           
        ,_RlXx     ( Xx       )                                                                                           
        ,_RlXy     ( Xy       )                                                                                           
        ,_RlAx     ( Ax       )                                                                                           
        ,_RlAy     ( Ay       )                                                                                           
        ,_DFr(
                {{ Xx._D.domResX                                  ,  //
                  (Xx._D.domResY > 0 ? Xx._D.domResY/2 + 1 : -1 ) ,  //
                  (Xx._D.domResZ > 0 ? Xx._D.domResZ/2 + 1 : -1 ) }} ,
                {{ Xx._D.lX   , Xx._D.lY   , Xx._D.lZ    }}           ,
                {{ Xx._D.orgnX, Xx._D.orgnY, Xx._D.orgnZ }}           )
        )
        ,_FrXx     (_DFr, "", "Ell_XEqGradR_and_LaplREqDivA_per_dft_fftw::_FrXx")
        ,_FrXy     (_DFr, "", "Ell_XEqGradR_and_LaplREqDivA_per_dft_fftw::_FrXy")
        ,_FrAx     (_DFr, "", "Ell_XEqGradR_and_LaplREqDivA_per_dft_fftw::_FrAx")
        ,_FrAy     (_DFr, "", "Ell_XEqGradR_and_LaplREqDivA_per_dft_fftw::_FrAy")
        //
        ,_frResX   ( _FrXx.get_arrResX() )
        ,_frResY   ( _FrXx.get_arrResY() )
        //
        ,_PlanXxBwd(  _FrXx, _RlXx, true )
        ,_PlanXyBwd(  _FrXy, _RlXy, true )
        ,_PlanAxFwd(  _RlAx, _FrAx, true )
        ,_PlanAyFwd(  _RlAy, _FrAy, true )
    {
        //Do nothing                                                                                                     
    }
    I find this very ugly but I am not sure about what to do else… The only alternative is to perform the initialisation within the constructor's definition, but that would require :
    1. a default constructor to be provided by MyClass, however the default constructor is implicitly deleted if MyClass owns a reference to an object/variable (unlike a pointer, a reference cannot be initialised to nullptr).
    2. all the objects owned by BroaderClass that aren't initialised in the initialiser list must be copy-assignable and/or move-assignable.


    By moving stuff within the constructor it looks already much nicer (plus, I can fold the constructor's definition)

    Code:
    Ell_XEqGradR_and_LaplREqDivA_per_dft_fftw::
    Ell_XEqGradR_and_LaplREqDivA_per_dft_fftw
            (/***/ DblArray & Xx
            ,/***/ DblArray & Xy
            ,/***/ DblArray & Ax
            ,/***/ DblArray & Ay
            )
        :_lX       ( Xx._D.lX )
        ,_lY       ( Xx._D.lY )                  
        ,_RlXx     ( & Xx     )
        ,_RlXy     ( & Xy     )
        ,_RlAx     ( & Ax     )
        ,_RlAy     ( & Ay     )
    { 
        _DFr = DomainRegCart(
                {{ Xx._D.domResX                                   ,  //
                   (Xx._D.domResY > 0 ? Xx._D.domResY/2 + 1 : -1 ) ,  //
                   (Xx._D.domResZ > 0 ? Xx._D.domResZ/2 + 1 : -1 ) }} , 
                {{ Xx._D.lX   , Xx._D.lY   , Xx._D.lZ    }}           ,
                {{ Xx._D.orgnX, Xx._D.orgnY, Xx._D.orgnZ }}           );
        //
        _FrXx = CplArrayFftw(_DFr, "", "Ell_XEqGradR_and_LaplREqDivA_per_dft_fftw::_FrXx");
        _FrXy = CplArrayFftw(_DFr, "", "Ell_XEqGradR_and_LaplREqDivA_per_dft_fftw::_FrXy");
        _FrAx = CplArrayFftw(_DFr, "", "Ell_XEqGradR_and_LaplREqDivA_per_dft_fftw::_FrAx");
        _FrAy = CplArrayFftw(_DFr, "", "Ell_XEqGradR_and_LaplREqDivA_per_dft_fftw::_FrAy");
        //
        _frResX = _FrXx.get_arrResX();
        _frResY = _FrXx.get_arrResY();
        //
        _PlanXxBwd = Intf_fftw_dftReal_bwd( _FrXx, _RlXx, true );
        _PlanXyBwd = Intf_fftw_dftReal_bwd( _FrXy, _RlXy, true );
        _PlanAxFwd = Intf_fftw_dftReal_fwd( _RlAx, _FrAx, true );
        _PlanAyFwd = Intf_fftw_dftReal_fwd( _RlAy, _FrAy, true );
    }
    However I require move-assignment operators in order to do this…
    How do you usually write your constructors and their initialisation lists ? Having an object with a deleted copy- and/or move-constructor is not usually a problem right ?

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 21
    Last Post: 01-22-2012, 11:35 AM
  2. Passing member variable as default arg to member function
    By Memloop in forum C++ Programming
    Replies: 1
    Last Post: 09-08-2009, 06:49 PM
  3. A struct member as a pointer to a variable
    By mc61 in forum C Programming
    Replies: 5
    Last Post: 01-27-2008, 08:40 AM
  4. Choosing a variable based on user text input.
    By Compiling... in forum C++ Programming
    Replies: 7
    Last Post: 11-01-2005, 01:21 AM
  5. Dereference pointer to void pointer to member
    By phil in forum C Programming
    Replies: 5
    Last Post: 04-20-2005, 11:54 AM

Tags for this Thread