带有指向内部数据成员的指针容器的 C++ 类:复制/赋值

C++ class with container of pointers to internal data members: copying/assignment

提问人:Ricardo Buring 提问时间:6/6/2018 更新时间:6/6/2018 访问量:534

问:

假设我有一个具有容器数据成员的类,以及另一个容器数据成员,其中包含指向 的可分辨元素的指针。特殊成员在构造函数中确定:Widgetd_membersd_special_membersd_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_membersd_members

  • 是否有必要重复在构造函数中完成的工作?我希望这可以避免。

  • 我可能想使用复制和交换的成语

  • 我想可以使用索引而不是指针,但是在我的实际用例中有一个类型(并且仍然只是,所以它指的是对的元素),所以这不是很方便。d_membersstd::vector< std::pair<int, int> >d_special_membersstd::vector<int*>

  • 只有(在施工时给出的)的现有内容由类修改;永远不会有任何重新分配(这将使指针无效)。d_members

  • 应该可以在运行时构造任意大小的对象。Widgetd_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
C++ 指针 复制构造函数赋 值运算符

评论

0赞 Ryan Haining 6/6/2018
移动构造函数可以通过首先使用 A 来完成,复制肯定是棘手的,而无需显式初始化unique_ptrd_membersd_special_members
2赞 Nir Friedman 6/6/2018
我不确定为什么存储索引,加上一个简单的私有成员函数,从索引到指针/引用,不方便?似乎比写出复制构造函数要好得多。
1赞 NathanOliver 6/6/2018
不是一个解决方案,但请记住,如果您修改d_special_membersd_members
0赞 sebrockm 6/6/2018
如果从未修改过,正如你所说,在代码中通过使其 const 来表达它可能是一个好主意?d_members
1赞 sebrockm 6/6/2018
@DanielLangr好吧,你更新你的评论比我回复它的速度快:)但显然我们也有同样的想法

答:

1赞 JaMiT 6/6/2018 #1

您要求的是一种在数据移动到新内存位置时维护关联的简单方法。正如你所发现的,指针远非理想的选择。您应该查找的是相对的东西,例如指向成员的指针。这并不完全适用于这种情况,所以我会选择我看到的最接近的替代方案:将索引存储到您的子结构中。因此,将索引存储到向量中,并存储一个指示对的第一个或第二个元素的标志(依此类推,如果您的结构变得更加复杂)。

我看到的另一种选择是遍历旧对象中的数据,以确定给定的特殊指针指向哪个元素 - 本质上是动态计算索引 - 然后在新对象中找到相应的元素并获取其地址。(也许您可以使用计算来加快速度,但我不确定这是否可移植。如果查找量很大,但复制量不大,则对整体性能可能更好。但是,我宁愿维护存储索引的代码。

1赞 Nir Friedman 6/6/2018 #2

最好的方法是使用索引。真诚地。它使移动和复制工作;这是一个非常有用的属性,因为在添加成员时,很容易获得手写副本的静默错误行为。将索引转换为引用/指针的私有成员函数似乎不是很繁琐。

也就是说,可能仍然存在类似的情况,指数不是一个好的选择。例如,如果你有一个而不是一个 ,你当然仍然可以存储键而不是指向值的指针,但这样你就会经历一个昂贵的哈希值。unordered_mapvector

如果你真的坚持使用指针而不是索引,我可能会这样做:

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(毫无疑问,也有例外)。

评论

0赞 sebrockm 6/6/2018
OP 想要使用 copy-and-swap-ideom。此解决方案具有不使用它的典型弱点。建议索引而不是指针是一个很好的方法,尽管imo。
1赞 Nir Friedman 6/6/2018
@sebrockm 典型的弱点到底是什么?
0赞 sebrockm 6/6/2018
哦,我错过了你的更新。Sry,对我来说太晚了。我倾向于同意你的观点:)
0赞 Daniel Langr 6/6/2018
我们不知道 OP 的动机,但我猜他想在 O(m) 中创建,其中 m 等于 。我肯定更喜欢您建议的索引,即使在对向量的情况下也是如此。d_special_membersd_special_members.size()
0赞 Nir Friedman 6/6/2018
我猜@DanielLangr,但这显然已经在普通成员中工作了,因为它们必须被复制。因此,复杂性实际上并没有改变。你当然可以计算东西,以避免这项工作,但恕我直言,这是非常令人讨厌的。我在这里的一般态度是,这个副本很慢,无论如何都不应该在关键路径上,所以我想要最简单、最干净的代码。当然,YMMV。
1赞 Daniel Langr 6/6/2018 #3

这对我来说很有效,尽管它非常丑陋,我永远不会在实际代码中使用它:vectorpair

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

1赞 sebrockm 6/6/2018 #4

我认为使用索引而不是指针是完美的。然后,您不需要任何自定义复制代码。 为方便起见,您可能希望定义一个成员函数,将索引转换为所需的实际指针。然后,您的成员可以具有任意复杂性。

private:
    int* getSpecialMemberPointerFromIndex(int specialIndex)
    {
        return &d_member[specialIndex];
    }