Reputation: 3265
Lets assume I am trying to create "immutable" class objects (i.e. members variables are defined with const
). So when calling the constructor I currently call separate init
functions to initialise the class members. But as a result there seems to be a lot of new vector-creating & vector-copying going on.
If the members weren't const
I could perform the initialisations in the { }
section of the constructor and write directly into values
(which I assume would be more efficient). But this is not possible.
Are there better/cleaner/more efficient ways to design the construction of immutable classes?
#include <vector>
class Data
{
public:
const std::vector<int> values;
Data(unsigned int size, int other) : values(init(size, other)) { }
Data(const std::vector<int>& other) : values(init(other)) { }
private:
std::vector<int> init(unsigned int size, int other) {
std::vector<int> myVector(size);
for (unsigned int i = 0; i < size; ++i)
myVector[i] = other * i;
return myVector;
}
std::vector<int> init(const std::vector<int>& other) {
std::vector<int> myVector(other);
for (unsigned int i = 0; i < other.size(); ++i)
myVector[i] *= myVector[i] - 1;
return myVector;
}
};
int main() {
Data myData1(5, 3); // gives {0, 3, 6, 9, 12}
Data myData2({2, 5, 9}); // gives {2, 20, 72}
return 0;
}
Upvotes: 0
Views: 189
Reputation: 6086
Your current design is perfectly fine. The initialization takes place in the constructor's member initialization list, so it will trigger a move at worst (which is quite cheap for a vector anyway) and NRVO at best.
NRVO is Named Return Value Optimization. When a function returns a named variable with automatic storage duration, the compiler is allowed to elide the copy/move. Note that however, copy/move constructors still need to be available even in the case of an elision. Here is a dummy example to sum up that concept:
SomeType foo() { // Return by value, no ref
SomeType some_var = ...; // some_var is a named variable
// with automatic storage duration
do_stuff_with(var);
return some_var; // NRVO can happen
}
(Your init
function follows that pattern.)
In C++17 you could even benefit from a guaranteed copy elision in that scenario depending on the shape of your init function. You can find out more about this in this other SO answer.
Note: Since you tagged your question c++11 I assume move semantics are available.
Upvotes: 2
Reputation: 148965
You say
there seems to be a lot of new vector-creating & vector-copying going on.
But I am unsure of it. I would instead expect one full creation and one move here:
init
builds and returns a temporary vector (ok full vector creation, directly with the final size) which is used to initialize the const member (ok a move should occur here). We should control the generated assembly here, but a decent compiler should build the data block once, and move it into the data member.
So unless you can prove by profiling (or by looking at the assembly generated by your compiler) that things really need to be optimized here, I would gladly keep on with this code because it clearly declares the member constness.
Upvotes: 1
Reputation: 435
The solution here is to remove const
from the member vector so that you can perform your initialisation in place, rather than by copying.
If you want values
to be readable but not writable by users of the class, you can expose a const
reference to it:
class Data {
std::vector<int> values_;
// constructors...
public:
std::vector<int> const& values() const { return values_; }
};
Upvotes: 0