Thread: Move-assignment operator on class with const-declared member variables

  1. #16
    C++ rookie Galdor's Avatar
    Join Date
    Apr 2016
    Location
    Belgium
    Posts
    32
    (I just noticed several posts appeared while I was writing mine, I will read them now)

  2. #17
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by Galdor
    So from what I understand calling reserve() might allocate more space than I truly need, right ?
    Yes, it is permitted for the implementation of reserve to do that. This gives latitude for implementations to make optimal decisions.

    Quote Originally Posted by Galdor
    So my fear is to allocate a lot more space than I really need.
    Increasing allocation by a factor is different thing from calling reserve: it is an implementation optimisation made in order for say, push_back to be an amortised constant operation as is required by the standard. Calling reserve usually serves one or both of two purposes: for correctness to ensure that insertion of elements does not cause re-allocation, hence invalidating iterators, and as an optimisation where re-allocation is not desirable. Hence, it is extremely unlikely that a call to reserve will allocate a lot more space than you really need, if your call to reserve specifies exactly what you need. In fact, if you want to create the elements as soon as possible, you don't even need to call reserve: you can simply invoke the constructor that creates N elements.

    Quote Originally Posted by Galdor
    Also, I still don't get it : from what I understand, you seem to say that it is never a good idea to declare a variable as a double[] array… Instead you recommend using a container class such as std::vector or std::deque. So why does the language leave me the possibility to create a variable of type double[] if it is never the best way to do things ? Is this due to historical reasons ?
    Refer to my post #12. If you have say, an array of average temperatures of each of the months in a year, declaring this is fine:
    Code:
    double temperatures[] = {23.4, /* ... */};
    Just that in modern C++, we might use a std::array<double, 12> instead because of the ways in which it behaves like other containers.

    In your case, you are dealing with new double[N], in which case either you use a container or encapsulate it in a smart pointer. If not, you have to do manual memory management yourself. But, the whole ability to do manual memory management then allows RAII to be implemented, i.e., the language cannot take this away without preventing std::vector from being implemented in the first place. But since std::vector is available, we should use it when appropriate.
    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. #18
    C++ rookie Galdor's Avatar
    Join Date
    Apr 2016
    Location
    Belgium
    Posts
    32
    Quote Originally Posted by laserlight
    That said, I notice that your class is named ArrayWrapper. I wonder if you are trying to practice implementing a simple dynamic array from scratch as a wrapper over a built-in dynamic array?
    No, the code I showed in my original post is dummy source code. In reality the ArrayWrapper class wraps a double[] (or std::vector or similar) and enhances it with a whole bunch of methods and variables. The ArrayWrapper class represents a grid discretisation of a 2D field function. For instance it stores temperature measurements taken at regular intervals on a flat rectangular surface. The array label is one of the information stored in the class, but it is far from being the only one : there is also the physical size of the domain, coordinates of origin, number of ghost nodes, etc. And there is a whole bunch of methods : load, save, add, substract, compute average, compute Euclidean norm, initialise using function of cartesian coordinates, etc. Eventually the class's actual name is "DblArray" as in "Array of double" (even though it should be better called "DblField" or something)

    Quote Originally Posted by laserlight
    King Mir qualified the suggestion with "If you run out of memory". So, I find your question rather inexplicable: you would prefer to bail out of whatever task the program is supposed to do with the memory because contiguous memory of the desired amount is not available rather than continue with a possible performance penalty because the memory is allocated in separate chunks?
    Unless std::deque allocates discontinuous chunks of memory even when there would be enough space for a contiguous allocation. However, judging by your reaction this is not case.

    Quote Originally Posted by Elysia
    Yeah, that's different. [...] Normally, other._lbl is of type const std::string. If we remove the const keyword, it becomes std::string (hereforth called T). [...] If you remove the const, std::move changes T to T&&, which matches the T&& constructor better than the const T& constructor, so it calls the move constructor.
    Very nice explanation ! It is all clear for me now !

  4. #19
    C++ rookie Galdor's Avatar
    Join Date
    Apr 2016
    Location
    Belgium
    Posts
    32
    Here is the new (dummy) code :

    Code:
    #include <string>
    #include <vector>
     
    class ArrayWrapper
    {
    private:
        std::string         _lbl; //Label for array
        std::vector<double> _arr; //Wrapped array
    public:
        ArrayWrapper(void)=default;
        ArrayWrapper(const std::string lbl) :_lbl(lbl),_arr(30) {}
        ~ArrayWrapper(void)=default;
        ArrayWrapper (const ArrayWrapper & other) :_lbl (other._lbl), _arr (other._arr) {}
    
        // MOVE CONSTRUCTOR
     
        ArrayWrapper
                (ArrayWrapper&& other
                )
            :_lbl ( std::move(other._lbl) )
            ,_arr ( std::move(other._arr) )
        {
            //Do nothing
        }
     
        // COPY-ASSIGNMENT OPERATOR
     
        ArrayWrapper &
        operator=
                (ArrayWrapper other
                )
        {
            std::swap( _lbl , other._lbl );
            std::swap( _arr , other._arr );
            return *this;
        }
     
        // MOVE-ASSIGNMENT OPERATOR
     
        ArrayWrapper &
        operator=
                (ArrayWrapper&& other
                )
        {
            std::swap( _lbl , other._lbl  );
            std::swap( _arr , other._arr  );
            return *this;
        }
    };
    
    int main(void) {
        return 0;
    }
    Does that look better ?

  5. #20
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    You don't need any assignment operators or copy/move constructors because the compiler will generate the correct ones for you.
    Also remove the (void) in the parameter lists. They're not needed.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  6. #21
    Registered User
    Join Date
    Apr 2006
    Posts
    2,149
    Quote Originally Posted by Galdor View Post
    Also, I still don't get it : from what I understand, you seem to say that it is never a good idea to declare a variable as a double[] array… Instead you recommend using a container class such as std::vector or std::deque. So why does the language leave me the possibility to create a variable of type double[] if it is never the best way to do things ? Is this due to historical reasons ?
    Several reasons:
    1) std::vector is a library feature that is implemented using dynamically allocated arrays (like the result of new double[N]); it is not a magic macro.
    2) The language gives you the tools to write a vector like container that may be better than std:vector for your particular project.
    3) historic reasons; old code still works
    4) It's not a design goal of the language to prevent people from intentionally violating what are though of as best practices
    5) C compatibility
    It is too clear and so it is hard to see.
    A dunce once searched for fire with a lighted lantern.
    Had he known what fire was,
    He could have cooked his rice much sooner.

  7. #22
    C++ rookie Galdor's Avatar
    Join Date
    Apr 2016
    Location
    Belgium
    Posts
    32
    Quote Originally Posted by Elysia
    You don't need any assignment operators or copy/move constructors because the compiler will generate the correct ones for you.
    Also remove the (void) in the parameter lists. They're not needed.
    Is that better : (I'm sorry but I really love specifying (void) in the parameter lists)

    Code:
    #include <iostream>
    #include <string>
    #include <vector>
      
    class ArrayWrapper
    {
    private:
        std::string         _lbl; //Label for array
        std::vector<double> _arr; //Wrapped array
    public:
        ~ArrayWrapper(void)=default;                               //Destructor
        ArrayWrapper(ArrayWrapper&& other)=default;                //Move constructor
        ArrayWrapper & operator=(ArrayWrapper&& other)=default;    //Move assignment
      
        // CONSTRUCTOR
    
        ArrayWrapper
                (const std::string lbl
                ,std::initializer_list<double> iniLs = {}
                )
            :_lbl(lbl  )
            ,_arr(iniLs)
        {
            //Do nothing
        }
    
        // COPY CONSTRUCTOR
    
        ArrayWrapper
                (const ArrayWrapper & other
                ,const std::string    lbl=""
                )
            :_lbl(lbl=="" ? other._lbl : lbl)
        {
            _arr.clear();
            std::copy(
                    other._arr.begin()       ,
                    other._arr.end()         ,
                    std::back_inserter(_arr) );
        }
    
        // COPY ASSIGNMENT
    
        ArrayWrapper &
        operator=
                (ArrayWrapper other
                )
        {
            std::swap(_arr, other._arr);
            return *this;
        }
        
        void
        dispContent(void)
        {
            std::cout << "Array \"" << _lbl << "\" : ";
            for(const auto elem : _arr) {
                std::cout << elem << ", ";
            }
            std::cout << std::endl;
        }
    };
     
    int main(void) {
        ArrayWrapper ArrA("Alpha", {1.0, 2.0, 3.0});                //
        ArrayWrapper ArrB("Beta" , {4.0, 5.0, 6.0});                //Constructor
        ArrayWrapper ArrC(ArrA, "Gamma");                           //Copy constructor
        ArrayWrapper ArrD("Delta");                                 //Constructor
        ArrayWrapper ArrE(ArrayWrapper("Epsilon", {11.0, 12.0} ));  //Move constructor
        //
        ArrA.dispContent();
        ArrB.dispContent();
        ArrC.dispContent();
        ArrD.dispContent();
        //
        ArrayWrapper ArrF = ArrayWrapper("Delta", {7.0, 8.0, 9.0}); //Move assignment
        ArrF.dispContent();
        //
        ArrF = ArrB; //Copy assignment
        ArrF.dispContent();
        //
        return 0;
    }
    Quote Originally Posted by King Mir
    Several reasons:
    1) std::vector is a library feature that is implemented using dynamically allocated arrays (like the result of new double[N]); it is not a magic macro.
    2) The language gives you the tools to write a vector like container that may be better than std:vector for your particular project.
    3) historic reasons; old code still works
    4) It's not a design goal of the language to prevent people from intentionally violating what are though of as best practices
    5) C compatibility
    Makes sense !

  8. #23
    Lurking whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    9,612
    I think you missed the point of Elysia's post. Your class is made up of things that C++ already knows how to copy or move. In effect, vector and string already have functions that will be called for you to do that... so you don't have to write a copy constructor or assignment operator or move constructor or move assignment. (All you're doing is writing down stuff that would happen anyway.)

  9. #24
    C++ rookie Galdor's Avatar
    Join Date
    Apr 2016
    Location
    Belgium
    Posts
    32
    Sorry for the late answer but I thought this topic was closed ; Apparently it's not…

    Quote Originally Posted by whiteflags View Post
    I think you missed the point of Elysia's post. Your class is made up of things that C++ already knows how to copy or move. In effect, vector and string already have functions that will be called for you to do that... so you don't have to write a copy constructor or assignment operator or move constructor or move assignment. (All you're doing is writing down stuff that would happen anyway.)
    Is this better ?

    IMHO I think I still need to define a copy constructor because I want to allow the user to set a label for the newly created array that can be different from the label of the copied array.

    Code:
    #include <iostream>
    #include <string>
    #include <vector>
       
    class ArrayWrapper
    {
    private:
        std::string         _lbl; //Label for array
        std::vector<double> _arr; //Wrapped array
    public:
        ~ArrayWrapper(void)=default;                               //Destructor
        ArrayWrapper(ArrayWrapper&& other)=default;                //Move constructor
        ArrayWrapper & operator=(ArrayWrapper&& other)=default;    //Move assignment
        ArrayWrapper & operator=(ArrayWrapper & other)=default;    //Copy assignment
       
        // CONSTRUCTOR
     
        ArrayWrapper
                (const std::string lbl
                ,std::initializer_list<double> iniLs = {}
                )
            :_lbl(lbl  )
            ,_arr(iniLs)
        {
            //Do nothing
        }
     
        // COPY CONSTRUCTOR
     
        ArrayWrapper
                (const ArrayWrapper & other
                ,const std::string    lbl=""
                )
            :_lbl(lbl=="" ? other._lbl : lbl)
        {
            _arr.clear();
            std::copy(
                    other._arr.begin()       ,
                    other._arr.end()         ,
                    std::back_inserter(_arr) );
        }
         
        void
        dispContent(void)
        {
            std::cout << "Array \"" << _lbl << "\" : ";
            for(const auto elem : _arr) {
                std::cout << elem << ", ";
            }
            std::cout << std::endl;
        }
    };
      
    int main(void) {
        ArrayWrapper ArrA("Alpha", {1.0, 2.0, 3.0});                //
        ArrayWrapper ArrB("Beta" , {4.0, 5.0, 6.0});                //Constructor
        ArrayWrapper ArrC(ArrA, "Gamma");                           //Copy constructor
        ArrayWrapper ArrD("Delta");                                 //Constructor
        ArrayWrapper ArrE(ArrayWrapper("Epsilon", {11.0, 12.0} ));  //Move constructor
        //
        ArrA.dispContent();
        ArrB.dispContent();
        ArrC.dispContent();
        ArrD.dispContent();
        //
        ArrayWrapper ArrF = ArrayWrapper("Delta", {7.0, 8.0, 9.0}); //Move assignment
        ArrF.dispContent();
        //
        ArrF = ArrB; //Copy assignment
        ArrF.dispContent();
        //
        return 0;
    }

  10. #25
    Registered User
    Join Date
    Oct 2006
    Posts
    3,445
    Quote Originally Posted by Galdor View Post
    IMHO I think I still need to define a copy constructor because I want to allow the user to set a label for the newly created array that can be different from the label of the copied array.
    That's not a copy constructor then. A copy constructor is defined as a constructor that accepts a const reference to an object of its own type, as its only parameter. Is there some reason why you can't just use the default copy constructor, and then call a member function to set the new label?
    What can this strange device be?
    When I touch it, it gives forth a sound
    It's got wires that vibrate and give music
    What can this thing be that I found?

  11. #26
    Registered User
    Join Date
    Apr 2006
    Posts
    2,149
    Quote Originally Posted by Elkvis View Post
    That's not a copy constructor then. A copy constructor is defined as a constructor that accepts a const reference to an object of its own type, as its only parameter. Is there some reason why you can't just use the default copy constructor, and then call a member function to set the new label?
    Why would he do that? That's splitting initialization.

    Having a constructor that takes a reference to an object of the same type, plus another parameter is perfectly fine.


    One thing you can take advantage of if you do this (in c++11) is delegating constructors; you can call the real copy constructor from your copy + label constructor, instead of listing every member variable that needs to be copied in the member initialization list.
    Last edited by King Mir; 05-24-2016 at 10:44 PM.
    It is too clear and so it is hard to see.
    A dunce once searched for fire with a lighted lantern.
    Had he known what fire was,
    He could have cooked his rice much sooner.

  12. #27
    Lurking whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    9,612
    in fact that is the only change I would really make; delegate constructors:
    Code:
    ArrayWrapper(ArrayWrapper const &) = default;
    
    ArrayWrapper(ArrayWrapper const & other, string const & label): ArrayWrapper(other)
    {
       _lbl = label;
    }
    The reason it's like this is it forces them to pass something other then the ``other" label if that's what they want. If they don't pass anything, then this isn't called, but the default copy constructor is called instead.

  13. #28
    Registered User
    Join Date
    Oct 2006
    Posts
    3,445
    Quote Originally Posted by King Mir View Post
    Why would he do that? That's splitting initialization.

    Having a constructor that takes a reference to an object of the same type, plus another parameter is perfectly fine.
    I never said it wasn't. The OP keeps talking about it as if it's a copy constructor though, and thinking that he/she needs a copy constructor, and technically, it's not a copy constructor - not one that the compiler would recognize as such, anyway.
    What can this strange device be?
    When I touch it, it gives forth a sound
    It's got wires that vibrate and give music
    What can this thing be that I found?

  14. #29
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    I never said it wasn't. The OP keeps talking about it as if it's a copy constructor though, and thinking that he/she needs a copy constructor, and technically, it's not a copy constructor - not one that the compiler would recognize as such, anyway.
    O_o

    The additional parameter of the function in question has a default value.

    The function is indeed the copy-constructor for the class, and any reasonably standard compiler will treat the function as such.

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

  15. #30
    C++ rookie Galdor's Avatar
    Join Date
    Apr 2016
    Location
    Belgium
    Posts
    32
    Quote Originally Posted by whiteflags
    in fact that is the only change I would really make; delegate constructors:

    Code:
         ArrayWrapper(ArrayWrapper const &) = default;
    
         ArrayWrapper(ArrayWrapper const & other, string const & label): ArrayWrapper(other)
         {
            _lbl = label;
         }
    I did try this but unfortunately it did not work… Clang++ returns following error :

    Code:
    main.cpp:49:10: error: call to constructor of 'ArrayWrapper' is ambiguous
    And g++ returns :
    Code:
    main.cpp:36:28: error: call of overloaded ‘ArrayWrapper(const ArrayWrapper&)’ is ambiguous
                 :ArrayWrapper(other)
    Which I can understand… So I am not sure that there can be a better way than what I showed in my previous post

    Code:
        // COPY CONSTRUCTOR
     
        ArrayWrapper
                (const ArrayWrapper & other
                ,const std::string    lbl=""
                )
            :_lbl(lbl=="" ? other._lbl : lbl)
        {
            _arr.clear();
            std::copy(
                    other._arr.begin()       ,
                    other._arr.end()         ,
                    std::back_inserter(_arr) );
        }
    Or perhaps there is ?

    Quote Originally Posted by Elkvis
    That's not a copy constructor then. A copy constructor is defined as a constructor that accepts a const reference to an object of its own type, as its only parameter.
    No, a copy constructor can have default arguments, judging from cppreference

    A copy constructor of class T is a non-template constructor whose first parameter is T&, const T&, volatile T&, or const volatile T&, and either there are no other parameters, or the rest of the parameters all have default values.
    and by the standard

    A non-template constructor for class X is a copy constructor if its first parameter is of type X&, const X&,
    volatile X& or const volatile X&, and either there are no other parameters or else all other parameters
    have default arguments (8.3.6). [ Example: X::X(const X&) and X::X(X&,int=1) are copy constructors.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 33
    Last Post: 09-28-2011, 05:17 AM
  2. Replies: 11
    Last Post: 05-02-2009, 09:23 AM
  3. Accessing a protected member declared in parent class
    By Canadian0469 in forum C++ Programming
    Replies: 3
    Last Post: 12-04-2008, 03:50 PM
  4. cannot access private member declared in class
    By newme in forum C++ Programming
    Replies: 7
    Last Post: 11-16-2008, 03:57 PM
  5. Replies: 2
    Last Post: 02-14-2008, 02:59 PM

Tags for this Thread