Компактный способ сделать Переменную не копируемой - PullRequest
0 голосов
/ 07 сентября 2018

В моем коде есть пара классов, из которых мне нужно копировать объекты.

Однако некоторые из этих классов используют данные, которые я хотел бы пропустить при копировании, например указатели владельца.

Пока что я нашел единственный способ сделать это либо полностью избежать копирования и создавать полностью новый объект вручную каждый раз, когда мне нужно скопировать его, либо обернуть указатель владельца в частную, не копируемую структуру , вот так:

class MyClass {
    // non-copyable owner
    struct Owner {
        Owner() = default;
        ~Owner() = default;

        Owner(const Owner& o) = delete;
        Owner& operator=(const Owner& o) = delete;

        SomeOtherClass* pointer = nullptr;
    };

    Owner owner = Owner();
}

Однако, делать это таким образом кажется немного многословным.

(Обратите внимание, что я не хотел бы использовать std::unique_ptr, поскольку я не хочу деконструировать владельца при деконструкции объекта)

Есть ли более компактный / эффективный / читабельный способ сделать это?

Редактировать: аннулирование default помеченных конструкторов и т. Д. Произошло из-за того, что я не инициализировал владельца значением по умолчанию. Глупо.

РЕДАКТИРОВАТЬ 2: Возможно, мне следует кое-что прояснить: указатель владельца должен указывать на объект, которому принадлежит MyClass, как способ для принадлежащего объекта ссылаться на своего владельца. А не наоборот. Вот почему я не хочу копировать указатель, поскольку объект с другим владельцем, который должен копировать этот объект, не должен менять объект, которому он принадлежит.

Принятый ответ позволяет мне сделать именно это. Я надеюсь, что это прояснило некоторую путаницу.

Ответы [ 4 ]

0 голосов
/ 07 сентября 2018

Владелец, которому не принадлежит ресурс? Похоже, вы врете в свой код, если я увижу это в коде, я думаю, у вас ошибка.

Тем не менее, unique_ptr действительно лучший способ справиться с этим. Вы можете дать ему собственный деструктор для закрытия ресурса (например, закрытие дескриптора файла). С удобочитаемой точки зрения это будет легче понять, чем использовать собственный класс.

Я не знаю, как вы определяете эффективность, однако, принимая во внимание эффективность во время выполнения, они не оказывают никакого влияния.

Наиболее компактный способ написания будет наследоваться от класса NonCopyable, у boost есть один, однако его очень легко написать, если вы не хотите эту зависимость.

С удобочитаемой точки зрения, я бы на самом деле пошел с вариантом вашей первоначальной реализации, а именно: следуйте правилу 5: укажите Ctor, скопируйте Ctor, переместите ctor, скопируйте назначить и переместите присвоение в вашем классе.

0 голосов
/ 07 сентября 2018

Может быть, что-то вроде фабричного метода хотя бы уклоняется от копирующей части во время компиляции?

// a method of MyClass
Owner * MyOwner()
{
     // check if created
     if(uidMap.find(parentObjectUid )==uidMap.end())
     {
         // create if needed
         uidMap.insert(std::pair<size_t,Owner *>(parentObjectUid ,new Owner()));
         // you can even use shared/unique ptr instead of new Owner()
         //    if you want automatic garbage
         return uidMap[parentObjectUid ];

     }
     else
     {
          return uidMap[parentObjectUid];
     }
}

тогда для создания uid может понадобиться нежелательный синглет или подобное глобальное синхронизированное производство. Таким образом, пока MyClass копируется с одинаковым идентификатором uid, обе копии будут иметь один и тот же объект Owner.

MyClass a;
MyClass b;
Owner * o = a.MyOwner(); // => creates
Owner * p = a.MyOwner(); // => uses
Owner * q = b.MyOwner(); // => creates another
a = b; // doesn't copy anything Owner

уникальный идентификатор может быть увеличенным 64-битным целым числом.

0 голосов
/ 07 сентября 2018

Копирование (и перемещение) имеют требуемую семантику из-за того, как работает elision, и в меньшей степени из-за того, как работают контейнеры.

Типы значений с хорошим поведением не должны вести себя на удивление иначе, если исключается копирование или перемещение. Они также должны вести себя хорошо при хранении в std::vector.

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

Отделить "состояние" ваших типов от их идентичности. Например, владение - это функция идентификации, а высота - это состояние.

Сохраните состояние в struct в вашем типе.

Идентификационные данные либо хранятся в вашем типе, либо в другой подструктуре. Подструктура идентификации должна иметь =delete операций копирования / перемещения.

Предоставить способ создания нового объекта с новым идентификатором, но с тем же состоянием. Если тип объекта - Foo, а его состояние - FooState, то у вас будет явный конструктор Foo(FooState).

Иметь метод, который создает копию состояния Foo.

Не разрешать копирование Foo. Семантика копирования Foo, когда идентичность изменяется (очищается) после копирования, является неработоспособной копией семантики . Foo не подлежит копированию.

Возможна семантика перемещения; в таком случае вы сообщаете своим владельцам об изменении вашего местоположения (через некоторый интерфейс), например:

if (owner)
  owner->child_moving( &old, this );
for( auto* child:children )
  child->parent_moving( &old, this );

, который позволяет родителям / детям обновлять указатели их владельцев / детей.

Если вы не делаете такого рода вещи, вы не хотите притворяться, что у вас семантика значений; удалите ваши операции копирования / перемещения вместо реализации безумных.

0 голосов
/ 07 сентября 2018

Вы можете использовать std::unique_ptr с удалителем, который ничего не делает.

template<typename T>
struct nop_deleter<T> {void operator()(T*){};};

class MyClass {
  std::unique_ptr<SomeType, nop_deleter<SomeType>> owner;
};

«Собственный» объект не будет деконструирован. (На данный момент он больше не принадлежит , но я просто педантичен).

Если вы хотите, чтобы он был немного более читабельным, вы всегда можете использовать псевдоним:

template<typename T>
struct nop_deleter<T> {void operator()(T*){};};

template<typename T>
using Owner = std::unique_ptr<T, nop_deleter<T>>;

class MyClass {
  Owner<SomeType> owner;
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...