提问人:Ricardo Buring 提问时间:6/6/2018 更新时间:6/6/2018 访问量:534
带有指向内部数据成员的指针容器的 C++ 类:复制/赋值
C++ class with container of pointers to internal data members: copying/assignment
问:
假设我有一个具有容器数据成员的类,以及另一个容器数据成员,其中包含指向 的可分辨元素的指针。特殊成员在构造函数中确定:Widget
d_members
d_special_members
d_members
#include <vector>
struct Widget
{
std::vector<int> d_members;
std::vector<int*> d_special_members;
Widget(std::vector<int> members) : d_members(members)
{
for (auto& member : d_members)
if (member % 2 == 0)
d_special_members.push_back(&member);
}
};
为这样的类实现复制构造函数和 operator=()
的最佳方法是什么?
副本中的 应指向 的副本。
d_special_members
d_members
是否有必要重复在构造函数中完成的工作?我希望这可以避免。
我可能想使用复制和交换的成语。
我想可以使用索引而不是指针,但是在我的实际用例中有一个类型(并且仍然只是,所以它指的是对的元素),所以这不是很方便。
d_members
std::vector< std::pair<int, int> >
d_special_members
std::vector<int*>
只有(在施工时给出的)的现有内容由类修改;永远不会有任何重新分配(这将使指针无效)。
d_members
应该可以在运行时构造任意大小的对象。
Widget
d_members
请注意,默认的赋值/复制只是复制指针:
#include <iostream>
using namespace std;
int main()
{
Widget w1({ 1, 2, 3, 4, 5 });
cout << "First special member of w1: " << *w1.d_special_members[0] << "\n";
Widget w2 = w1;
*w2.d_special_members[0] = 3;
cout << "First special member of w1: " << *w1.d_special_members[0] << "\n";
}
收益 率
First special member of w1: 2
First special member of w1: 3
答:
您要求的是一种在数据移动到新内存位置时维护关联的简单方法。正如你所发现的,指针远非理想的选择。您应该查找的是相对的东西,例如指向成员的指针。这并不完全适用于这种情况,所以我会选择我看到的最接近的替代方案:将索引存储到您的子结构中。因此,将索引存储到向量中,并存储一个指示对的第一个或第二个元素的标志(依此类推,如果您的结构变得更加复杂)。
我看到的另一种选择是遍历旧对象中的数据,以确定给定的特殊指针指向哪个元素 - 本质上是动态计算索引 - 然后在新对象中找到相应的元素并获取其地址。(也许您可以使用计算来加快速度,但我不确定这是否可移植。如果查找量很大,但复制量不大,则对整体性能可能更好。但是,我宁愿维护存储索引的代码。
最好的方法是使用索引。真诚地。它使移动和复制工作;这是一个非常有用的属性,因为在添加成员时,很容易获得手写副本的静默错误行为。将索引转换为引用/指针的私有成员函数似乎不是很繁琐。
也就是说,可能仍然存在类似的情况,指数不是一个好的选择。例如,如果你有一个而不是一个 ,你当然仍然可以存储键而不是指向值的指针,但这样你就会经历一个昂贵的哈希值。unordered_map
vector
如果你真的坚持使用指针而不是索引,我可能会这样做:
struct Widget
{
std::vector<int> d_members;
std::vector<int*> d_special_members;
Widget(std::vector<int> members) : d_members(members)
{
for (auto& member : d_members)
if (member % 2 == 0)
d_special_members.push_back(&member);
}
Widget(const Widget& other)
: d_members(other.d_members)
, d_special_members(new_special(other))
{}
Widget& operator=(const Widget& other) {
d_members = other.d_members;
d_special_members = new_special(other);
}
private:
vector<int*> new_special(const Widget& other) {
std::vector<int*> v;
v.reserve(other.d_special_members.size());
std::size_t special_index = 0;
for (std::size_t i = 0; i != d_members.size(); ++i) {
if (&other.d_members[i] == other.d_special_members[special_index]) {
v.push_back(&d_members[i});
++special_index;
}
}
return v;
}
};
我的实现在线性时间内运行,不使用额外的空间,但利用了指针中没有重复的事实(基于您的示例代码),并且指针的顺序与原始数据相同。
我避免复制和交换,因为没有必要避免代码重复,而且没有任何理由。获得强大的异常安全性可能会对性能造成影响,仅此而已。但是,编写一个泛型 CAS 来为任何正确实现的类提供强大的异常安全性是微不足道的。类编写者通常不应该对赋值运算符使用 copy 和 swap(毫无疑问,也有例外)。
评论
d_special_members
d_special_members.size()
这对我来说很有效,尽管它非常丑陋,我永远不会在实际代码中使用它:vector
pair
std::vector<std::pair<int, int>> d_members;
std::vector<int*> d_special_members;
Widget(const Widget& other) : d_members(other.d_members) {
d_special_members.reserve(other.d_special_members.size());
for (const auto p : other.d_special_members) {
ptrdiff_t diff = (char*)p - (char*)(&other.d_members[0]);
d_special_members.push_back((int*)((char*)(&d_members[0]) + diff));
}
}
为了简洁起见,我只使用了类似 C 的 cast 会更好。我不确定这个解决方案是否不会导致未定义的行为,事实上我猜它确实如此,但我敢说大多数编译器都会生成一个工作程序。reinterpret_cast
我认为使用索引而不是指针是完美的。然后,您不需要任何自定义复制代码。 为方便起见,您可能希望定义一个成员函数,将索引转换为所需的实际指针。然后,您的成员可以具有任意复杂性。
private:
int* getSpecialMemberPointerFromIndex(int specialIndex)
{
return &d_member[specialIndex];
}
上一个:C、赋值结构指针改变赋值右侧内容
下一个:对数组使用算术
评论
unique_ptr
d_members
d_special_members
d_special_members
d_members
d_members