让我们假设我正在尝试创建“不可变的”类对象(即,成员变量是用const
定义的)。因此,当调用构造函数时,我当前调用单独的init
函数来初始化类成员。但是结果是似乎出现了许多新的向量创建和向量复制。
如果成员不是const
,则可以在构造函数的{ }
部分中执行初始化,然后直接写入values
(我认为这样会更有效)。但这是不可能的。
有没有更好/更干净/更有效的方法来设计不可变类的构造?
#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;
}
答案 0 :(得分:2)
您当前的设计非常好。初始化发生在构造函数的成员初始化列表中,因此它将在最坏的情况下触发移动(无论如何对于矢量来说这都是相当便宜的),而在最佳情况下会触发 NRVO 。
NRVO 是 命名返回值优化 。当函数返回具有自动存储持续时间的命名变量时,允许编译器取消复制/移动。但是请注意,即使在省略的情况下,复制/移动构造函数仍然需要可用。这是一个概括这个概念的虚拟示例:
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
}
(您的init
函数遵循该模式。)
在C ++ 17中,根据init函数的形状,您甚至可以从这种情况下受益于保证的复制省略。您可以在其他中找到有关此内容的更多信息。
注意:由于您标记了问题SO answer,因此我认为移动语义可用。
答案 1 :(得分:1)
你说
似乎有很多新的向量创建和向量复制正在进行。
但是我不确定。相反,我希望在这里进行一次完整的创作,然后采取以下行动:
init
生成并返回一个临时向量(可以创建完整的向量,直接使用最终大小),该向量用于初始化const成员(可以在此处进行移动)。我们应该在这里控制生成的程序集,但是一个不错的编译器应该一次构建数据块,然后将其移动到数据成员中。
因此,除非您可以通过概要分析(或通过查看编译器生成的程序集)证明确实需要在此处进行优化,否则我将很高兴继续使用此代码,因为它清楚地声明了成员常量。
答案 2 :(得分:0)
这里的解决方案是从成员向量中删除const
,以便您可以就地执行初始化,而不是通过复制来完成。
如果您希望values
可以被该类的用户阅读但不能写,则可以公开对其的const
引用:
class Data {
std::vector<int> values_;
// constructors...
public:
std::vector<int> const& values() const { return values_; }
};