Skip to content

Assignment Operator C++ Const Member Initialization

Member Functions[edit]

Member functions can (and should) be used to interact with data contained within user defined types. User defined types provide flexibility in the "divide and conquer" scheme in program writing. In other words, one programmer can write a user defined type and guarantee an interface. Another programmer can write the main program with that expected interface. The two pieces are put together and compiled for usage. User defined types provide encapsulation defined in the Object Oriented Programming (OOP) paradigm.

Within classes, to protect the data members, the programmer can define functions to perform the operations on those data members. Member functions and functions are names used interchangeably in reference to classes. Function prototypes are declared within the class definition. These prototypes can take the form of non-class functions as well as class suitable prototypes. Functions can be declared and defined within the class definition. However, most functions can have very large definitions and make the class very unreadable. Therefore it is possible to define the function outside of the class definition using the scope resolution operator "::". This scope resolution operator allows a programmer to define the functions somewhere else. This can allow the programmer to provide a header file .h defining the class and a .obj file built from the compiled .cpp file which contains the function definitions. This can hide the implementation and prevent tampering. The user would have to define every function again to change the implementation. Functions within classes can access and modify (unless the function is constant) data members without declaring them, because the data members are already declared in the class.

Simple example:

file: Foo.h

// the header file named the same as the class helps locate classes within a project// one class per header file makes it easier to keep the // header file readable (some classes can become large)// each programmer should determine what style works for them or what programming standards their// teacher/professor/employer has#ifndef FOO_H#define FOO_HclassFoo{public:Foo();// function called the default constructorFoo(inta,intb);// function called the overloaded constructorintManipulate(intg,inth);private:intx;inty;};#endif

file: Foo.cpp

#include"Foo.h"/* these constructors should really show use of initialization listsFoo::Foo() : x(5), y(10){}Foo::Foo(int a, int b) : x(a), y(b){}*/Foo::Foo(){x=5;y=10;}Foo::Foo(inta,intb){x=a;y=b;}intFoo::Manipulate(intg,inth){x=h+g*x;y=g+h*y;}

Overloading[edit]

Member functions can be overloaded. This means that multiple member functions can exist with the same name on the same scope, but must have different signatures. A member function's signature is comprised of the member function's name and the type and order of the member function's parameters.

Due to name hiding, if a member in the derived class shares the same name with members of the base class, they will be hidden to the compiler. To make those members visible, one can use declarations to introduce them from base class scopes.

Constructors and other class member functions, except the Destructor, can be overloaded.

Constructors[edit]

A constructor is a special member function that is called whenever a new instance of a class is created. The compiler calls the constructor after the new object has been allocated in memory, and converts that "raw" memory into a proper, typed object. The constructor is declared much like a normal member function but it will share the name of the class and it has no return value.

Constructors are responsible for almost all of the run-time setup necessary for the class operation. Its main purpose becomes in general defining the data members upon object instantiation (when an object is declared), they can also have arguments, if the programmer so chooses. If a constructor has arguments, then they should also be added to the declaration of any other object of that class when using the new operator. Constructors can also be overloaded.

FoomyTest;// essentially what happens is: Foo myTest = Foo();FoomyTest(3,54);// accessing the overloaded constructorFoomyTest=Foo(20,45);// although a new object is created, there are some extra function calls involved// with more complex classes, an assignment operator should// be defined to ensure a proper copy (includes ''deep copy'')// myTest would be constructed with the default constructor, and then the// assignment operator copies the unnamed Foo( 20, 45 ) object to myTest

using new with a constructor

Foo*myTest=newFoo();// this defines a pointer to a dynamically allocated objectFoo*myTest=newFoo(40,34);// constructed with Foo( 40, 34 )// be sure to use delete to avoid memory leaks

Note:

While there is no risk in using new to create an object, it is often best to avoid using memory allocation functions within objects' constructors. Specifically, using new to create an array of objects, each of which also uses new to allocate memory during its construction, often results in runtime errors. If a class or structure contains members that must be pointed at dynamically created objects, it is best to sequentially initialize these arrays of the parent object, rather than leaving the task to their constructors.
This is especially important when writing code with exceptions (in exception handling), if an exception is thrown before a constructor is completed, the associated destructor will not be called for that object.

A constructor can't delegate to another. It is also considered desirable to reduce the use of default arguments, if a maintainer has to write and maintain multiple constructors it can result in code duplication, which reduces maintainability because of the potential for introducing inconsistencies and even lead to code bloat.

Default Constructors

A default constructor is one which can be called with no arguments. Most commonly, a default constructor is declared without any parameters, but it is also possible for a constructor with parameters to be a default constructor if all of those parameters are given default values.

In order to create an array of objects of a class type, the class must have an accessible default constructor; C++ has no syntax to specify constructor arguments for array elements.

Overloaded Constructors[edit]

When an object of a class is instantiated, the class writer can provide various constructors each with a different purpose. A large class would have many data members, some of which may or may not be defined when an object is instantiated. Anyway, each project will vary, so a programmer should investigate various possibilities when providing constructors.

These are all constructors for a class myFoo.

myFoo();// default constructor, the user has no control over initial values// overloaded constructorsmyFoo(inta,intb=0);// allows construction with a certain 'a' value, but accepts 'b' as 0// or allows the user to provide both 'a' and 'b' values// ormyFoo(inta,intb);// overloaded constructor, the user must specify both valuesclassmyFoo{private:intUseful1;intUseful2;public:myFoo(){// default constructorUseful1=5;Useful2=10;};myFoo(inta,intb=0){// two possible cases when invokedUseful1=a;Useful2=b;};};myFooFind;// default constructor, private member values Useful1 = 5, Useful2 = 10myFooFind(8);// overloaded constructor case 1, private member values Useful1 = 8, Useful2 = 0myFooFind(8,256);// overloaded constructor case 2, private member values Useful1 = 8, Useful2 = 256

Constructor initialization lists[edit]

Constructor initialization lists (or member initialization list) are the only way to initialize data members and base classes with a non-default constructor. Constructors for the members are included between the argument list and the body of the constructor (separated from the argument list by a colon). Using the initialization lists is not only better in terms of efficiency but also the simplest way to guarantee that all initialization of data members are done before entering the body of constructors.

// Using the initialization list for myComplexMember_ MyClass::MyClass(intmySimpleMember,MyComplexClassmyComplexMember):myComplexMember_(myComplexMember)// only 1 call, to the copy constructor{mySimpleMember_=mySimpleMember;// uses 2 calls, one for the constructor of the mySimpleMember class// and a second for the assignment operator of the MyComplexClass class}

This is more efficient than assigning value to the complex data member inside the body of the constructor because in that case the variable is initialized with its corresponding constructor.

Note that the arguments provided to the constructors of the members do not need to be arguments to the constructor of the class; they can also be constants. Therefore you can create a default constructor for a class containing a member with no default constructor.

Example:

MyClass::MyClass():myComplexMember(0)_{}

It is useful to initialize your members in the constructor using this initialization lists. This makes it obvious for the reader that the constructor does not execute logic. The order the initialization is done should be the same as you defined your base-classes and members. Otherwise you can get warnings at compile-time. Once you start initializing your members make sure to keep all in the constructor(s) to avoid confusion and possible 0xbaadfood.

It is safe to use constructor parameters that are named like members.

Example:

classMyClass:publicMyBaseClassA,publicMyBaseClassB{private:intc;void*pointerMember;public:MyClass(int,int,int);};/*...*/MyClass::MyClass(inta,intb,intc):MyBaseClassA(a),MyBaseClassB(b),c(c),pointerMember(NULL),referenceMember(){//logic}

Note that this technique was also possible for normal functions but it is now obsoleted and is classified as an error in such case.

Note:
It is a common misunderstanding that initialization of data members can be done within the body of constructors. All such kind of so-called "initialization" are actually assignments. The C++ standard defines that all initialization of data members are done before entering the body of constructors. This is the reason why certain types (const types and references) cannot be assigned to and must be initialized in the constructor initialization list.

One should also keep in mind that class members are initialized in the order they are declared, not the order they appear in the initializer list. One way of avoiding chicken and egg paradoxes is to always add the members to the initializer list in the same order they're declared.

Destructors[edit]

Destructors like the Constructors are declared as any normal member functions but will share the same name as the Class, what distinguishes them is that the Destructor's name is preceded with a "~", it can not have arguments and can't be overloaded.

Destructors are called whenever an Object of the Class is destroyed. Destructors are crucial in avoiding resource leaks (by deallocating memory), and in implementing the RAII idiom. Resources which are allocated in a Constructor of a Class are usually released in the Destructor of that Class as to return the system to some known or stable state after the Class ceases to exist.

The Destructor is invoked when Objects are destroyed, after the function they were declared in returns, when the delete operator is used or when the program is over. If an object of a derived type is destructed, first the Destructor of the most derived object is executed. Then member objects and base class subjects are destructed recursively, in the reverse order their corresponding Constructors completed. As with structs the compiler implicitly declares a Destructor as an inline public member of its class if the class doesn’t have a user-declared Destructor.

The dynamic type of the object will change from the most derived type as Destructors run, symmetrically to how it changes as Constructors execute. This affects the functions called by virtual calls during construction and destruction, and leads to the common (and reasonable) advice to avoid calling virtual functions of an object either directly or indirectly from its Constructors or Destructors.

[edit]

Sharing most of the concepts we have seen before on the introduction to inline functions, when dealing with member function those concepts are extended, with a few additional considerations.

If the member functions definition is included inside the declaration of the class, that function is by default made implicitly inline. Compiler options may override this behavior.

Calls to virtual functions cannot be inlined if the object's type is not known at compile-time, because we don't know which function to inline.

[edit]

The static keyword can be used in four different ways:

To do:
Alter the above links from subsection to book locations after the structure is fixed.

static member function[edit]

Member functions or variables declared static are shared between all instances of an object type. Meaning that only one copy of the member function or variable does exists for any object type.

member functions callable without an object

When used in a class function member, the function does not take an instantiation as an implicit parameter, instead behaving like a free function. This means that static class functions can be called without creating instances of the class:

classFoo{public:Foo(){++numFoos;cout<<"We have now created "<<numFoos<<" instances of the Foo class\n";}staticintgetNumFoos(){returnnumFoos;}private:staticintnumFoos;};intFoo::numFoos=0;// allocate memory for numFoos, and initialize itintmain(){Foof1;Foof2;Foof3;cout<<"So far, we've made "<<Foo::getNumFoos()<<" instances of the Foo class\n";}
Named constructors[edit]

Named constructors are a good example of using static member functions. Named constructors is the name given to functions used to create an object of a class without (directly) using its constructors. This might be used for the following:

  1. To circumvent the restriction that constructors can be overloaded only if their signatures differ.
  2. Making the class non-inheritable by making the constructors private.
  3. Preventing stack allocation by making constructors private

Declare a static member function that uses a private constructor to create the object and return it. (It could also return a pointer or a reference but this complication seems useless, and turns this into the factory pattern rather than a conventional named constructor.)

Here's an example for a class that stores a temperature that can be specified in any of the different temperature scales.

classTemperature{public:staticTemperatureFahrenheit(doublef);staticTemperatureCelsius(doublec);staticTemperatureKelvin(doublek);private:Temperature(doubletemp);double_temp;};Temperature::Temperature(doubletemp):_temp(temp){}TemperatureTemperature::Fahrenheit(doublef){returnTemperature((f+459.67)/1.8);}TemperatureTemperature::Celsius(doublec){returnTemperature(c+273.15);}TemperatureTemperature::Kelvin(doublek){returnTemperature(k);}

const[edit]

This type of member function cannot modify the member variables of a class. It's a hint both to the programmer and the compiler that a given member function doesn't change the internal state of a class; however, any variables declared as can still be modified.

Take for example:

classFoo{public:intvalue()const{returnm_value;}voidsetValue(inti){m_value=i;}private:intm_value;};

Here clearly does not change m_value and as such can and should be const. However does modify m_value and as such cannot be const.

Another subtlety often missed is a member function cannot call a non-const member function (and the compiler will complain if you try). The member function cannot change member variables and a non-const member functions can change member variables. Since we assume non-const member functions do change member variables, member functions are assumed to never change member variables and can't call functions that do change member variables.

The following code example explains what can do depending on where it is placed.

classFoo{public:/* * Modifies m_widget and the user * may modify the returned widget. */Widget*widget();/* * Does not modify m_widget but the * user may modify the returned widget. */Widget*widget()const;/* * Modifies m_widget, but the user * may not modify the returned widget. */constWidget*cWidget();/* * Does not modify m_widget and the user * may not modify the returned widget. */constWidget*cWidget()const;private:Widget*m_widget;};

Accessors and Modifiers (Setter/Getter)[edit]

What is an accessor?
An accessor is a member function that does not modify the state of an object. The accessor functions should be declared as const.
Getter is another common definition of an accessor due to the naming ( ) of that type of member functions.
What is a modifier?
A modifier, also called a modifying function, is a member function that changes the value of at least one data member. In other words, an operation that modifies the state of an object. Modifiers are also known as ‘mutators’.
Setter is another common definition of a modifier due to the naming ( ) of that type of member functions.

Note:
These are commonly used reference labels (not defined on the standard language).

Dynamic polymorphism (Overrides)[edit]

So far, we have learned that we can add new data and functions to a class through inheritance. But what about if we want our derived class to inherit a method from the base class, but to have a different implementation for it? That is when we are talking about polymorphism, a fundamental concept in OOP programming.

As seen previously in the Programming Paradigms Section, Polymorphism is subdivided in two concepts static polymorphism and dynamic polymorphism. This section concentrates on dynamic polymorphism, which applies in C++ when a derived class overrides a function declared in a base class.

We implement this concept redefining the method in the derived class. However, we need to have some considerations when we do this, so now we must introduce the concepts of dynamic binding, static binding and virtual methods.

Suppose that we have two classes, and . derives from and redefines the implementation of a method that resides in class . Now suppose that we have an object of class . How should the instruction be interpreted?

If is declared in the stack (not declared as a pointer or a reference) the compiler applies static binding, this means it interprets (at compile time) that we refer to the implementation of that resides in .

However, if we declare as a pointer or a reference of class , the compiler could not know which method to call at compile time, because can be of type or . If this is resolved at run time, the method that resides in will be called. This is called dynamic binding. If this is resolved at compile time, the method that resides in A will be called. This is again, static binding.

Virtual member functions[edit]

The member functions is relatively simple, but often misunderstood. The concept is an essential part of designing a class hierarchy in regards to sub-classing classes as it determines the behavior of overridden methods in certain contexts.

Virtual member functions are class member functions, that can be overridden in any class derived from the one where they were declared. The member function body is then replaced with a new set of implementation in the derived class.

Note:
When overriding virtual functions you can alter the private, protected or public state access state of the member function of the derived class.

By placing the keyword before a method declaration we are indicating that when the compiler has to decide between applying static binding or dynamic binding it will apply dynamic binding. Otherwise, static binding will be applied.

Note:
While it is not required to use the virtual keyword in our subclass definitions (since if the base class function is virtual all subclass overrides of it will also be virtual) it is good style to do so when producing code for future reutilization (for use outside of the same project).

Again, this should be clearer with an example:

classFoo{public:voidf(){std::cout<<"Foo::f()"<<std::endl;}virtualvoidg(){std::cout<<"Foo::g()"<<std::endl;}};classBar:publicFoo{public:voidf(){std::cout<<"Bar::f()"<<std::endl;}virtualvoidg(){std::cout<<"Bar::g()"<<std::endl;}};intmain(){Foofoo;Barbar;Foo*baz=&bar;Bar*quux=&bar;foo.f();// "Foo::f()"foo.g();// "Foo::g()"bar.f();// "Bar::f()"bar.g();// "Bar::g()"// So far everything we would expect...baz->f();// "Foo::f()"baz->g();// "Bar::g()"quux->f();// "Bar::f()"quux->g();// "Bar::g()"return0;}

Our first calls to and on the two objects are straightforward. However things get interesting with our baz pointer which is a pointer to the Foo type.

is not and as such a call to will always invoke the implementation associated with the pointer type—in this case the implementation from Foo.

Virtual function calls are computationally more expensive than regular function calls. Virtual functions use pointer indirection, invocation and will require a few extra instructions than normal member functions. They also require that the constructor of any class/structure containing virtual functions to initialize a table of pointers to its virtual member functions.

All this characteristics will signify a trade-off between performance and design. One should avoid preemptively declaring functions virtual without an existing structural need. Keep in mind that virtual functions that are only resolved at run-time cannot be inlined.

To do:
Example of issue of virtual and inline.

Note:
Some of the needs for using virtual functions can be addressed by using a class template. This will be covered when we introduce Templates.

Pure virtual member function[edit]

There is one additional interesting possibility. Sometimes we don't want to provide an implementation of our function at all, but want to require people sub-classing our class to provide an implementation on their own. This is the case for pure virtuals.

To indicate a pure function instead of an implementation we simply add an "" after the function declaration.

Again—an example:

classWidget{public:virtualvoidpaint()=0;};classButton:publicWidget{public:voidpaint()// is virtual because it is an override{// do some stuff to draw a button}};

Because is a pure function in the Widget we are required to provide an implementation in all concrete subclasses. If we don't the compiler will give us an error at build time.

This is helpful for providing interfaces—things that we expect from all of the objects based on a certain hierarchy, but when we want to ignore the implementation details.

So why is this useful?

Let's take our example from above where we had a pure for painting. There are a lot of cases where we want to be able to do things with widgets without worrying about what kind of widget it is. Painting is an easy example.

Imagine that we have something in our application that repaints widgets when they become active. It would just work with pointers to widgets—i.e. might be a possible function signature. So we might do something like:

Widget*w=window->activeWidget();w->paint();

We want to actually call the appropriate paint member function for the "real" widget type—not (which is a "pure" and will cause the program to crash if called using virtual dispatch). By using a function we insure that the member function implementation for our subclass -- in this case—will be called.

To do:
Mention interface classes

Covariant return types[edit]

Covariant return types is the ability for a virtual function in a derived class to return a pointer or reference to an instance of itself if the version of the method in the base class does so. e.g.

classbase{public:virtualbase*create()const;};classderived:publicbase{public:virtualderived*create()const;};

This allows casting to be avoided.

Note:
Some older compilers do not have support for covariant return types. Workarounds exist for such compilers.

virtual Constructors[edit]

There is a hierarchy of classes with base class . Given an object belonging in the hierarchy, it is desired to be able to do the following:

  1. Create an object of the same class as (say, class ) initialized using the default constructor of the class. The syntax normally used is:
  2. Create an object of the same class as which is a copy of . The syntax normally used is:

In the class , the methods and are declared as follows:

classFoo{// ...public:// Virtual default constructorvirtualFoo*create()const;// Virtual copy constructorvirtualFoo*clone()const;};

If is to be used as an abstract class, the functions may be made pure virtual:

classFoo{// ...public:virtualFoo*create()const=0;virtualFoo*clone()const=0;};

In order to support the creation of a default-initialized object, and the creation of a copy object, each class in the hierarchy must have public default and copy constructors. The virtual constructors of are defined as follows:

classBar:...// Bar is a descendant of Foo{// ...public:// Non-virtual default constructorBar();// Non-virtual copy constructorBar(constBar&);// Virtual default constructor, inline implementationBar*create()const{returnnewFoo();}// Virtual copy constructor, inline implementationBar*clone()const{returnnewFoo(*this);}};

The above code uses covariant return types. If your compiler doesn't support , use instead, and similarly for .

While using these virtual constructors, you must manually deallocate the object created by calling . This hassle could be avoided if a smart pointer (e.g. ) is used in the return type instead of the plain old .

Remember that whether or not uses dynamically allocated memory, you must define the destructor and make it to take care of deallocation of objects using pointers to an ancestral type.

virtual Destructor[edit]

It is of special importance to remember to define a virtual destructor even if empty in any base class, since failing to do so will create problems with the default compiler generated destructor that will not be virtual.

A virtual destructor is not overridden when redefined in a derived class, the definitions to each destructor are cumulative and they start from the last derivate class toward the first base class.

Pure virtual Destructor[edit]

Every abstract class should contain the declaration of a pure virtual destructor.

Pure virtual destructors are a special case of pure virtual functions (meant to be overridden in a derived class). They must always be defined and that definition should always be empty.

classInterface{public:virtual~Interface()=0;//declaration of a pure virtual destructor };Interface::~Interface(){}//pure virtual destructor definition (should always be empty)

Law of three[edit]

The "law of three" is not really a law, but rather a guideline: if a class needs an explicitly declared copy constructor, copy assignment operator, or destructor, then it usually needs all three.

There are exceptions to this rule (or, to look at it another way, refinements). For example, sometimes a destructor is explicitly declared just in order to make it ; in that case there's not necessarily a need to declare or implement the copy constructor and copy assignment operator.

Most classes should not declare any of the "big three" operations; classes that manage resources generally need all three.


  1. C++ rookie
    Join Date
    Apr 2016
    Location
    Belgium
    Posts
    32

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

    Hi all,

    I am doing a small exercise before I start implementing the rule of three (five in my case) in a larger code. However the class I am interested in has constant member variables. The reason why I declared them as const is simply that for a given object they will never change during the object's lifetime, yet the values they will hold are not known at compile-time. However, the member variables' const specifier prevents me from implementing "properly" the move-assignement operator (at least, as far as I know…). For similar reasons I can't use the copy-swap idiom for the copy constructor…

    #include <memory> #include <string> class ArrayWrapper { private: const std::strin _lbl; //Label for array std::unique_ptr<double> _arr; //Wrapped array public: //DUMMY CONSTRUCTOR ArrayWrapper(void) { //Do nothing } // "REGULAR" CONSTRUCTOR ArrayWrapper (const std::string lbl //in :Array label ) :_lbl (lbl ) { _arr.reset( new double[10] ); } // DESTRUCTOR //No need for explicit destructor (?) // COPY CONSTRUCTOR ArrayWrapper (const ArrayWrapper & other //in :Array to copy ) :_lbl (other._lbl ) { std::copy( other._arr.get() , other._arr.get() + 10 , _arr.get() ); } // MOVE CONSTRUCTOR ArrayWrapper (ArrayWrapper&& other ) :_lbl ( std::move(other._lbl) ) //No compiler error but will const-declared _lbl actually be moved ?! ,_arr ( std::move(other._arr) ) { //Do nothing } // COPY-ASSIGNMENT OPERATOR ArrayWrapper & operator= (ArrayWrapper other //in :Array to assign ) { std::swap( _lbl , other._lbl ); //Compiler error because _lbl is declared as const return *this; } // MOVE-ASSIGNMENT OPERATOR ArrayWrapper & operator= (ArrayWrapper&& other ) { std::swap( _lbl , other._lbl ); //Compiler error because _lbl is declared as const return *this; } };
    I can emulate the behaviour of such functions by copying the data. However that defeats the purpose of the move-assignment operator…

    //Copy-assignment operator ArrayWrapper operator= (const ArrayWrapper & other //in :Array to assign ) { ArrayWrapper Arr(other._lbl); std::copy( other._arr.get() , other._arr.get() + 10 , _arr.get() ); return Arr; } //Move assignment operator ArrayWrapper operator= (ArrayWrapper && other ) { ArrayWrapper Arr(other._lbl); std::copy( other._arr.get() , other._arr.get() + 10 , _arr.get() ); return Arr; }

    To add to my confusion, here is someone who is claiming that const member variables should only be used in "const classes" (not quite sure what is meant by "const class"). He/She also say that if a class is const then it is fine to not implement the assignment operators (which appears to me as going against the rule of five…).

    On a side note, could someone confirm (or refute) that since I don't have any pointers (but merely a smart pointer) I don't need to explicitly declare/implement the destructor. However, doing that would go against the rule of three (five)…

    So here is a shorter list of all my questions so far :
    • How should I implement the move assignment operator on a class with const member variables ?
    • Is it ok to call std::move() in the move constructor in order to initialise a const member variable by swapping its value with another const member variable ? (Compiler does not complain although I was expecting it to)
    • Do I need to implement a destructor whenever I have a class containing exclusively member variables that are automatically destructed ?


    Looking forward for your answers !

  2. Lurking
    Join Date
    Apr 2006
    Location
    United States
    Posts
    9,572
    Last edited by whiteflags; 05-09-2016 at 09:12 AM.
    I think a move will be fine, however, I'm inclined to agree with the poster on Stack Overflow. Constant members in classes have very limited utility. Particularly in your case, making the array label a normal mutable string and encapsulating where it changes is the same as the variable being const to the user, because they can set it in the constructor and forget about it.

    class ArrayWrapper { private: std::string label_; std::size_t size_; std::unique_pointer<double[]> arr_; // I also think you should be using this type, since the pointer is simulating an array. public: ArrayWrapper(size_t size, std::string label) : label_(label), size_(size > 0 ? size : 10), arr_(new double[size]) { } void setLabel(string label) { label_ = label; } std::string getLabel() const { return label_; } };
    Something like that. If the user never calls setLabel(), then it is as if label_ is const. Anywhere you can simplify your class's design choices, I really feel like you should.

    There are other strategies with varying degrees of usefulness, because nevertheless, true constants do appear in classes, rarely. The clean solution is to deal with them with outside of the class proper; that is they aren't really like the rest of the members. Like having a public enum for the class and the user to use, or using a constant template argument. Or they are static members. Static members can be effectively const because all of the objects would share the same member instance.

    std::move is exactly like static_cast<>ing to an r-value reference. I.e. if it compiles, it should work. The only other thing that you have to be careful for is not to do this:
    string s = "my label"; s = std::move(s); // undefined: move is not required to perform self checks

  3. C++ rookie
    Join Date
    Apr 2016
    Location
    Belgium
    Posts
    32
    But what about optimisation ? Does it make a difference to the compiler if the member variables are constant or not ? I always thought that I needed to be as restrictive as I possibly could… (and that's how I ended up declaring my member variables as const wherever possible…)

    So if I want to prevent the user from modifying label_ then I am not required to encapsulate it, correct ? On the same topic : what is considered good practice :
    • to define a setters and getters for every members of the class
    • or to define setters and getters for some members of the class only ?


    From what I understand from the examples you cite, it's never something like
    class MyClass { const double myVar };
    but the most similar acceptable solution would be
    class MyClass { static const double myVar };
    (or enum or template argument, but they look radically different). Did I understand you properly ?

    Good point !

    That's what I'm trying to do, but unfortunately since I am not completely familiar with the language yet that's not always how my code actually ends up being…
    Originally Posted by whiteflags
    Particularly in your case, making the array label a normal mutable string and encapsulating where it changes is the same as the variable being const to the user, because they can set it in the constructor and forget about it.
    Originally Posted by whiteflags
    If the user never calls setLabel(), then it is as if label_ is const
    Originally Posted by whiteflags
    There are other strategies with varying degrees of usefulness, because nevertheless, true constants do appear in classes, rarely
    Originally Posted by whiteflags
    std::unique_pointer<double[]> arr_; // I also think you should be using this type, since the pointer is simulating an array.
    Originally Posted by whiteflags
    Anywhere you can simplify your class's design choices, I really feel like you should.

  4. C++ Witch
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    25,789
    Look up a C++ Reference and learn How To Ask Questions The Smart Way
    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.
    That was probably just a poor choice of words: the term "const class" or phrase "class is const" is not well defined in C++ terminology. However, from context (e.g., the next sentence) we can figure out what it means: a class for which any object of the class is logically constant.

    No, it does not go against the rule if you disable the assignment operators, e.g., by declaring them deleted and/or private.
    Originally Posted by Galdor
    To add to my confusion, here is someone who is claiming that const member variables should only be used in "const classes" (not quite sure what is meant by "const class").
    Originally Posted by Galdor
    He/She also say that if a class is const then it is fine to not implement the assignment operators (which appears to me as going against the rule of five…).

  5. Lurking
    Join Date
    Apr 2006
    Location
    United States
    Posts
    9,572
    Last edited by whiteflags; 05-09-2016 at 11:25 AM.
    I think you are acting prematurely. It is not clear what you want to optimize yet. If for example you are thinking about optimizing the code that displays the label, or you want to make memory reads as fast as they can be, well, these general purpose operations are basically as fast as they can be already. You should wait for a performance problem to appear before you worry about fixing it.

    You have it backwards. Since you want to make it so the label cannot change, the easiest way to do that is to make the label private, provide a get method, but not provide a set method at all. This enforces that the label does not change (whereas in previous code I didn't enforce it).

    It is preferable that you hide class data behind methods such as get and set. It basically limits the surface area of the variable's impact. The point of a get method is to control where class data is used and ensures that the data is used in a read only way. You know that calling the get method is not going to cause the object to change, so the method is innocent when you are looking for and fixing bugs. You can remove the method from consideration while you are debugging.

    The point of a set method is to control where and how the object changes. In your array wrapper class, it's not easy to explain get and set because basically any label will work. Still, set methods are where you can enforce business rules. Say for example that another class had an email data member; where you set that member you might want to ensure that the string had an @ in the middle, and maybe check that the domain was one of a few white-listed domains. The email example is a good one because it proves that set methods can be as complicated as you want.

    In real code, the functions might not be called getFoo() and setFoo() but the idea is the same.

    You understood what I had to say about class constants being static members.
    But what about optimisation ? Does it make a difference to the compiler if the member variables are constant or not ?
    So if I want to prevent the user from modifying label_ then I am not required to encapsulate it, correct ? On the same topic : what is considered good practice :

  6. C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    You shouldn't because const says the member will not change ever after construction. That means you're do assignment since that, by definition, modifies the instance. If you don't want the data to change after construction, but allow copy/move assignment, then make the variables private, but non-const, then provide only getters as mentioned already.

    No! The code compiles because you're making a copy! Assignment modifies the instance! With your code, if I do

    MyClass a, b;
    a = b;

    a will not actually equal b. Instead, the operator will return a new instance, c, which will be equal to a. This is not intuitive and goes against what we're taught about assignment.

    No you don't. But the thing is - BECAUSE you're using RAII pattern here, there is no need for any copy constructor, move constructor copy or move assignment operators. The compiler will generate all the code automatically. The point is that if you need to do something special with your members, you probably need to implement special logic in all places.

    Btw, use std::vector for your array. As it stands now, you're just asking for trouble if you change the array size. You will most likely end up with a bug.

    As for getters and setters... you shouldn't make them out of habit for every member. You should strive to define what kind of data you want your class to return. If that information just means returning a member variable, then make a getter for that, then. But don't blindly make getters and setters for every variable.
    Originally Posted by Galdor
    How should I implement the move assignment operator on a class with const member variables ?
    Is it ok to call std::move() in the move constructor in order to initialise a const member variable by swapping its value with another const member variable ? (Compiler does not complain although I was expecting it to)
    Do I need to implement a destructor whenever I have a class containing exclusively member variables that are automatically destructed ?

  7. C++ rookie
    Join Date
    Apr 2016
    Location
    Belgium
    Posts
    32
    Yet, whether the object should be constant or not is a choice made by the class user, not by the person who wrote the class… I can't prevent the user from creating non-const objects, right ?

    All right !

    Okey dokey !

    Sure, up to now I declared as many of the member variables as private and defined the least getter and setter methods possible, only using them when I could not think of any better alternative.

    All right !

    I thought the member variable

    std::unique_ptr<double[]> _arr;
    was the thing special that required me to implement that "special logic" : I want to deep copy the array's content (or move it)… You are implying it is not necessary to do so ?

    In my case the array's dimensions never change after instantiation. Also it can be a fairly big array (up to 15,000,000 items) so I'm not sure std::vector is the better option. Do you think otherwise ?

    I'm not sure we are talking about the same thing : that remark of mine concerned the move constructor,

    ArrayWrapper (ArrayWrapper&& other ) :_lbl ( std::move(other._lbl) ) //No compiler error but will const-declared _lbl actually be moved ?! ,_arr ( std::move(other._arr) ) { //Do nothing }
    not the copy- or move-assignment operators… Here std::move() is called with other._lbl as an argument… eventhough ArrayWrapper::_lbl is declared as const and hence shouldn't be moved ? (at least that what I expected but to my surprise the compiler seemed happy with it)

    On the same subject :

    @whiteflags I didn't quite understand that bit…
    Originally Posted by laserlight
    However, from context (e.g., the next sentence) we can figure out what it means: a class for which any object of the class is logically constant.
    Originally Posted by laserlight
    No, it does not go against the rule if you disable the assignment operators, e.g., by declaring them deleted and/or private.
    Originally Posted by whiteflags
    I think you are acting prematurely. It is not clear what you want to optimize yet. If for example you are thinking about optimizing the code that displays the label, or you want to make memory reads as fast as they can be, well, these general purpose operations are basically as fast as they can be already. You should wait for a performance problem to appear before you worry about fixing it.
    Originally Posted by whiteflags
    It is preferable that you hide class data behind methods such as get and set.
    Originally Posted by Elysia
    As for getters and setters... you shouldn't make them out of habit for every member.
    Originally Posted by Elysia
    BECAUSE you're using RAII pattern here, there is no need for any copy constructor, move constructor copy or move assignment operators. The compiler will generate all the code automatically. The point is that if you need to do something special with your members, you probably need to implement special logic in all places.
    Originally Posted by Elysia
    Btw, use std::vector for your array. As it stands now, you're just asking for trouble if you change the array size. You will most likely end up with a bug.
    Originally Posted by Elysia
    No! The code compiles because you're making a copy!
    Originally Posted by whiteflags
    std::move is exactly like static_cast<>ing to an r-value reference.

  8. Registered User
    Join Date
    Apr 2006
    Posts
    2,146
    Last edited by King Mir; 05-09-2016 at 06:04 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.
    Vector is still a good option, but you should reserve() that number of elements before you build the array. There's no cost to having, but not using, the ability to add elements after initialization.

    If you run out of memory, you might consider using a deque. It has the same iterator guarantees, but it will fragment your large array, allowing it to use discontinuous memory.

    That's right, no move will occur.

    std::move would probably be better named "move_cast". It cast an object to a "throw-away" value, which in technical parlance is called an r-value reference. You can have a const throw-away value, so operation is fine, however, you cannot actually move from a const value, because that involves modifying it. In the language of types calling std::move here casts _lbl from "const string &" to "const string &&", which then calls the "const string &" string constructor, because that's the best match.
    In my case the array's dimensions never change after instantiation. Also it can be a fairly big array (up to 15,000,000 items) so I'm not sure std::vector is the better option. Do you think otherwise ?
    Originally Posted by Galdor
    I'm not sure we are talking about the same thing : that remark of mine concerned the move constructor,

    ArrayWrapper (ArrayWrapper&& other ) :_lbl ( std::move(other._lbl) ) //No compiler error but will const-declared _lbl actually be moved ?! ,_arr ( std::move(other._arr) ) { //Do nothing }
    not the copy- or move-assignment operators… Here std::move() is called with other._lbl as an argument… eventhough ArrayWrapper::_lbl is declared as const and hence shouldn't be moved ? (at least that what I expected but to my surprise the compiler seemed happy with it)

    On the same subject :



    @whiteflags I didn't quite understand that bit…

  9. C++ rookie
    Join Date
    Apr 2016
    Location
    Belgium
    Posts
    32
    What about the time required to access vector elements ? How does it compare with double[] arrays ? And does the CPU cache the data as efficiently with std::vector as it does with double[] arrays ? (I.e. I tend to use several neighbooring values of the array one after the other.) If std::vector has so many advantages then why do people keep on using double[] typed variables ? (Unless they don't ?)

    I saw that on cppreference.com :

    In my case memory can become a scarce resource… In the sense that it happened before that my RAM was completely or almost completely full…

    Interesting to know !
    But having discontinuous memory will probably impair CPU caching, right ?

    So what you are saying is that std::move() attempts to move a value and if it can't then the value is copied ? In my case, the compiler is happy because even though it can't move the value, it can still copy it ?

    At least that's what I understand when reading this :

    Originally Posted by King Mir
    Vector is still a good option, but you should reserve() that number of elements before you build the array. There's no cost to having, but not using, the ability to add elements after initialization.
    Originally Posted by cppreference.com
    Vectors usually occupy more space than static arrays, because more memory is allocated to handle future growth.
    Originally Posted by King Mir
    If you run out of memory, you might consider using a deque. It has the same iterator guarantees, but it will fragment your large array, allowing it to use discontinuous memory.
    Originally Posted by King Mir
    std::move would probably be better named "move_cast". It cast an object to a "throw-away" value, which in technical parlance is called an r-value reference. You can have a const throw-away value, so operation is fine, however, you cannot actually move from a const value, because that involves modifying it. In the language of types calling std::move here casts _lbl from "const string &" to "const string &&", which then calls the "const string &" string constructor, because that's the best match.
    Originally Posted by artima
    This move() gives its target the value of its argument, but is not obliged to preserve the value of its source. So, for a vector, move() could reasonably be expected to leave its argument as a zero-capacity vector to avoid having to copy all the elements. In other words, move is a potentially destructive read.
    Originally Posted by artima
    It is now up to client code to overload key functions on whether their argument is an lvalue or rvalue (e.g. copy constructor and assignment operator). When the argument is an lvalue, the argument must be copied from. When it is an rvalue, it can safely be moved from.

  10. Registered User
    Join Date
    Apr 2006
    Posts
    2,146
    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.
    A quality implementation of reserve() should only allocate as much memory as you ask for. However, a vector does store the size of memory allocated and the number of elements, so there is a tiny amount of memory overhead, you're right.

    Not so much; it's not a linked list; it's an array/list of arrays. So your array is devided into chunks, and reading from the same chunk does not impair caching. Besides, your whole array does not fit into cache anyway. But there are some additional costs to iterating.



    Yep.
    Originally Posted by Galdor
    What about the time required to access vector elements ? How does it compare with double[] arrays ? And does the CPU cache the data as efficiently with std::vector as it does with double[] arrays ? (I.e. I tend to use several neighbooring values of the array one after the other.) If std::vector has so many advantages then why do people keep on using double[] typed variables ? (Unless they don't ?)

    I saw that on cppreference.com :



    In my case memory can become a scarce resource… In the sense that it happened before that my RAM was completely or almost completely full…
    Interesting to know !
    But having discontinuous memory will probably impair CPU caching, right ?
    So what you are saying is that std::move() attempts to move a value and if it can't then the value is copied ? In my case, the compiler is happy because even though it can't move the value, it can still copy it ?

    At least that's what I understand when reading this :

  11. C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Sorry, I wasn't clear. If you deep copy semantics, then you're right, you need special logic. If you want a moveable-only type (which is what unique_ptr implies), then you don't need to do anything.

    It it almost always the better option. It does come with a slight overhead (it needs to store the size and a pointer to the data), but that's it. It handles deep copying, reallocation and deallocation by itself. In case you only need a specific size, you can explicitly size the vector (or reserve).

    Yeah, that's different. In C++, copying and moving is handled by calling an appropriate function (either constructor or assignment operator). So how does the compiler know which function to call? By doing its usual function overloading evaluation, by looking at the arguments types.
    Normally, other._lbl is of type const std::string. If we remove the const keyword, it becomes std::string (hereforth called T). So you have const T or T, then the compiler looks at the constructors and sees two versions: one that accepts const T& and one that accepts T&&. There is no implicit conversion T -> T&&, so the move constructor is ruled out. There is an implicit conversion T/const T -> const T&, so the copy constructor is called.
    What std::move does is change the type from T to T&&, and that's it. If you've got const T, then it changes the type to const T&&. There is no implicit conversion const T&& -> T&&, so the move constructor is, again, ruled out, leaving only const T&& -> const T& for the copy constructor, where there IS an implicit conversion. Therefore, it compiles and it calls the copy constructor instead of the move constructor. 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.
    Originally Posted by Galdor
    I thought the member variable

    std::unique_ptr<double[]> _arr;
    was the thing special that required me to implement that "special logic" : I want to deep copy the array's content (or move it)… You are implying it is not necessary to do so ?
    In my case the array's dimensions never change after instantiation. Also it can be a fairly big array (up to 15,000,000 items) so I'm not sure std::vector is the better option. Do you think otherwise ?
    I'm not sure we are talking about the same thing : that remark of mine concerned the move constructor,

    ArrayWrapper (ArrayWrapper&& other ) :_lbl ( std::move(other._lbl) ) //No compiler error but will const-declared _lbl actually be moved ?! ,_arr ( std::move(other._arr) ) { //Do nothing }
    not the copy- or move-assignment operators… Here std::move() is called with other._lbl as an argument… eventhough ArrayWrapper::_lbl is declared as const and hence shouldn't be moved ? (at least that what I expected but to my surprise the compiler seemed happy with it)

  12. C++ Witch
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    25,789
    If these matter to you, you should measure and find out for yourself as implementations can vary. That said, assuming that optimisations are enabled, NDEBUG is defined, and checked iterators are disabled, the difference is likely to be negligible in the bigger picture, especially since you appear to be doing dynamic memory allocation. std::vector may even be better if you involve class types instead of double because you are not using placement new, so you may end up constructing objects that do not need to be constructed.

    If the size of the array that is to be in use is fixed and known at compile time (e.g., an array of all the month names), then a fixed size array, whether built-in or std::array, could be more appropriate than std::vector. Basically, std::vector is intended to be the "default" dynamic array container.

    Originally Posted by Galdor
    What about the time required to access vector elements ? How does it compare with double[] arrays ? And does the CPU cache the data as efficiently with std::vector as it does with double[] arrays ? (I.e. I tend to use several neighbooring values of the array one after the other.)
    Originally Posted by Galdor
    If std::vector has so many advantages then why do people keep on using double[] typed variables ? (Unless they don't ?)