简体   繁体   中英

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

Suppose I have a class Widget with a container data member d_members , and another container data member d_special_members containing pointers to distinguished elements of d_members . The special members are determined in the constructor:

#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);
    }
};

What is the best way to implement the copy constructor and operator=() for such a class?

  • The d_special_members in the copy should point to the copy of d_members .

  • Is it necessary to repeat the work that was done in the constructor? I hope this can be avoided.

  • I would probably like to use the copy-and-swap idiom .

  • I guess one could use indices instead of pointers, but in my actual use case d_members has a type like std::vector< std::pair<int, int> > (and d_special_members is still just std::vector<int*> , so it refers to elements of pairs), so this would not be very convenient.

  • Only the existing contents of d_members (as given at construction time) are modified by the class; there is never any reallocation (which would invalidate the pointers).

  • It should be possible to construct Widget objects with d_members of arbitrary size at runtime.


Note that the default assignment/copy just copies the pointers:

#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";
}

yields

First special member of w1: 2
First special member of w1: 3

What you are asking for is an easy way to maintain associations as data is moved to new memory locations. Pointers are far from ideal for this, as you have discovered. What you should be looking for is something relative, like a pointer-to-member. That doesn't quite apply in this case, so I would go with the closest alternative I see: store indices into your sub-structures. So store an index into the vector and a flag indicating the first or second element of the pair (and so on, if your structure gets even more complex).

The other alternative I see is to traverse the data in the old object to figure out which element a given special pointer points to -- essentially computing the indices on the fly -- then find the corresponding element in the new object and take its address. (Maybe you could use a calculation to speed this up, but I'm not sure that would be portable.) If there is a lot of lookup and not much copying, this might be better for overall performance. However, I would rather maintain the code that stores indices.

The best way is to use indices. Honestly. It makes moves and copies just work; this is a very useful property because it's so easy to get silently wrong behavior with hand written copies when you add members. A private member function that converts an index into a reference/pointer does not seem very onerous.

That said, there may still be similar situations where indices aren't such a good option. If you, for example have a unordered_map instead of a vector , you could of course still store the keys rather than pointers to the values, but then you are going through an expensive hash.

If you really insist on using pointers rather that indices, I'd probably do this:

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;
    }
};

My implementation runs in linear time and uses no extra space, but exploits the fact (based on your sample code) that there are no repeats in the pointers, and that the pointers are ordered the same as the original data.

I avoid copy and swap because it's not necessary to avoid code duplication and there just isn't any reason for it. It's a possible performance hit to get strong exception safety, that's all. However, writing a generic CAS that gives you strong exception safety with any correctly implemented class is trivial. Class writers should usually not use copy and swap for the assignment operator (there are, no doubt, exceptions).

This work for me for vector of pair s, though it's terribly ugly and I would never use it in real code:

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));
   }
}

For sake of brevity I used only C-like type cast, reinterpret_cast would be better. I am not sure whether this solution does not result in undefined behavior , in fact I guess it does, but I dare to say that most compilers will generate a working program.

I think using indexes instead of pointers is just perfect. You don't need any custom copy code then. For convenience you may want to define a member function converting the index to actual pointer you want. Then your members can be of arbitrary complexity.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM