Класс C ++ с контейнером указателей на внутренние элементы данных: копирование / присваивание - PullRequest
0 голосов
/ 06 июня 2018

Предположим, у меня есть класс 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

Ответы [ 4 ]

0 голосов
/ 06 июня 2018

Я думаю, что использование индексов вместо указателей просто идеально.Вам не нужно никакого специального кода для копирования.Для удобства вы можете определить функцию-член, конвертирующую индекс в нужный вам указатель.Тогда ваши участники могут быть произвольной сложности.

private:
    int* getSpecialMemberPointerFromIndex(int specialIndex)
    {
        return &d_member[specialIndex];
    }
0 голосов
/ 06 июня 2018

Лучший способ - использовать индексы.Честно.Это делает движения и копии просто работают;это очень полезное свойство, потому что при добавлении членов очень легко получить неверное поведение в случае рукописных копий.Закрытая функция-член, которая преобразует индекс в ссылку / указатель, выглядит не очень обременительно.

Тем не менее, могут существовать аналогичные ситуации, когда индексы не являются хорошим вариантом.Если, например, у вас есть 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 для оператора присваивания (есть, без сомнения, исключения).

0 голосов
/ 06 июня 2018

Это работает для меня для 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-likeнаберите, reinterpret_cast будет лучше.Я не уверен, что это решение не приведет к неопределенному поведению , на самом деле, я полагаю, что так оно и есть, но я осмелюсь сказать, что большинство компиляторов генерируют работающую программу.

0 голосов
/ 06 июня 2018

То, что вы просите, - это простой способ поддерживать ассоциации, когда данные перемещаются в новые области памяти.Как вы уже обнаружили, указатели далеко не идеальны для этого.То, что вы должны искать, это нечто относительное, например указатель на член.Это не совсем применимо в этом случае, поэтому я бы выбрал ближайшую альтернативу: хранить индексы в ваших подструктурах.Поэтому сохраните индекс в векторе и флаг, указывающий первый или второй элемент пары (и так далее, если ваша структура становится еще более сложной).

Другая альтернатива, которую я вижу, состоит в том, чтобы проходить данные встарый объект, чтобы выяснить, на какой элемент указывает данный специальный указатель - по сути, вычисляя индексы на лету, - затем найти соответствующий элемент в новом объекте и взять его адрес.(Может быть, вы могли бы использовать вычисления, чтобы ускорить это, но я не уверен, что это будет переносимым.) Если поиск много и копирование мало, это может быть лучше для общей производительности.Однако я бы предпочел сохранить код, который хранит индексы.

...