有效地初始化 const std::vector 类成员



假设我正在尝试创建"不可变"类对象(即成员变量是用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;
}

您当前的设计非常好。初始化发生在构造函数的成员初始化列表中,因此它最坏的情况是触发移动(无论如何,这对于向量来说是相当便宜的(和充其量是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答案中找到有关此内容的更多信息。

注意:由于您标记了问题c ++ 11,因此我认为移动语义可用。

你说

似乎有很多新的矢量创建和矢量复制正在进行中。

但我不确定。相反,我期望在这里完成一次创作和一次移动:init构建并返回一个临时向量(确定完整向量创建,直接使用最终大小(,用于初始化 const 成员(确定此处应该发生移动(。我们应该在这里控制生成的程序集,但是一个体面的编译器应该构建一次数据块,并将其移动到数据成员中。

因此,除非您可以通过分析(或通过查看编译器生成的程序集(来证明这里确实需要优化,否则我很乐意继续使用此代码,因为它清楚地声明了成员恒常性。

这里的解决方案是从成员向量中删除const,以便您可以就地执行初始化,而不是通过复制。

如果希望类的用户values可读但不可写,则可以公开对它的const引用:

class Data {
std::vector<int> values_;
// constructors...
public:
std::vector<int> const& values() const { return values_; }
};

最新更新