Каковы преимущества boost::noncopyable

Чтобы предотвратить копирование класса, вы можете очень легко объявить закрытый конструктор копирования / операторы присваивания. Но вы также можете наследовать boost::noncopyable,

Каковы преимущества / недостатки использования наддува в этом случае?

11 ответов

Решение

Подводя итог тому, что сказали другие:

Преимущества boost::noncopyable по частным методам копирования:

  1. Это более явно и описательно в намерении. Использование функций частного копирования - это идиома, которая занимает больше времени, чем обнаружение noncopyable,
  2. Это меньше кода / меньше ввода / меньше беспорядка / меньше места для ошибок (проще всего было бы случайно обеспечить реализацию).
  3. Он встраивает значение прямо в метаданные типа, аналогично атрибуту C#. Теперь вы можете написать функцию, которая принимает только объекты, которые не могут быть скопированы.
  4. Это потенциально ловит ошибки ранее в процессе сборки. Ошибка будет отображаться во время компиляции, а не во время компоновки, в случае, если сам класс или его друзья делают ошибочное копирование.
  5. (почти так же, как #4) Предотвращает сам класс или его друзей от вызова методов private copy.

Преимущества методов частного копирования перед boost::noncopyable:

  1. Нет повышения зависимости

Я не вижу никакой выгоды от документации:

#include <boost/noncopyable.hpp>

struct A
    : private boost::noncopyable
{
};

против:

struct A
{
     A(const A&) = delete;
     A& operator=(const A&) = delete;
};

Когда вы добавляете типы только для перемещения, я даже вижу документацию вводящей в заблуждение. Следующие два примера не могут быть скопированы, хотя они являются подвижными:

#include <boost/noncopyable.hpp>

struct A
    : private boost::noncopyable
{
    A(A&&) = default;
    A& operator=(A&&) = default;
};

против:

struct A
{
    A(A&&) = default;
    A& operator=(A&&) = default;
};

При множественном наследовании может быть даже штраф за пробел:

#include <boost/noncopyable.hpp>

struct A
    : private boost::noncopyable
{
};

struct B
    : public A
{
    B();
    B(const B&);
    B& operator=(const B&);
};

struct C
    : public A
{
};

struct D
    : public B,
      public C,
      private boost::noncopyable
{
};

#include <iostream>

int main()
{
    std::cout << sizeof(D) << '\n';
}

Для меня это распечатывает:

3

Но вот что, я считаю, имеет превосходную документацию:

struct A
{
    A(const A&) = delete;
    A& operator=(const A&) = delete;
};

struct B
    : public A
{
    B();
    B(const B&);
    B& operator=(const B&);
};

struct C
    : public A
{
    C(const C&) = delete;
    C& operator=(const C&) = delete;
};

struct D
    : public B,
      public C
{
    D(const D&) = delete;
    D& operator=(const D&) = delete;
};

#include <iostream>

int main()
{
    std::cout << sizeof(D) << '\n';
}

Выходы:

2

Мне гораздо проще объявить свои операции копирования, чем рассуждать, извлекаю ли я из boost::non_copyable несколько раз, и если это будет стоить мне. Особенно, если я не являюсь автором полной иерархии наследования.

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

  1. Цель boost::noncopyable более ясна.
  2. Boost:: noncopyable предотвращает случайное использование методов классов конструктором закрытого копирования.
  3. Меньше кода с boost::noncopyable.

Я не могу понять, почему никто другой, кажется, не упоминает об этом, но:

С noncopyable Вы пишете название своего класса только один раз.

Без пятикратного дублирования: один A для "класса A", два для отключения назначения и два для отключения конструктора копирования.

Цитирование документации:

"Традиционный способ справиться с этим - объявить конструктор частной копии и присвоение копии, а затем задокументировать, почему это делается. Но вывод из некопируемого проще и понятнее, и не требует дополнительной документации".

http://www.boost.org/libs/utility/utility.htm

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

Это также предотвращает случайное определение функций (т.е. {} вместо ;), небольшая ошибка, которая может остаться незамеченной, но которая позволит членам и друзьям делать недействительные копии объекта.

Небольшой недостаток (специфичный для GCC) заключается в том, что если вы компилируете свою программу с g++ -Weffc++ и у вас есть классы, содержащие указатели, например,

class C : boost::noncopyable
{
public:
  C() : p(nullptr) {}

private:
  int *p;
};

GCC не понимает, что происходит:

предупреждение: "класс C" имеет указатели на данные-члены [-WeffC++]
предупреждение: но не переопределяет 'C(const S&)' [-WeffC++]
предупреждение: или 'operator=(const C&)' [-WeffC++]

Пока не буду жаловаться на:

#define DISALLOW_COPY_AND_ASSIGN(Class) \
  Class(const Class &) = delete;     \
  Class &operator=(const Class &) = delete

class C
{
public:
  C() : p(nullptr) {}
  DISALLOW_COPY_AND_ASSIGN(C);

private:
  int *p;
};

PS Я знаю, что у GCC -WeffC++ есть несколько проблем. Код, который проверяет "проблемы", довольно упрощен, во всяком случае... иногда это помогает.

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

Я бы лучше использовал boost:: noncopyable, чем вручную удалять или приватизировать конструктор копирования и оператор присваивания.

Тем не менее, я почти никогда не использую ни один из этих методов, потому что:

Если я делаю не копируемый объект, должна быть причина, по которой он не копируется. Эта причина, в 99% случаев, в том, что у меня есть участники, которые не могут быть скопированы осмысленно. Скорее всего, такие члены также будут лучше подходить как частные детали реализации. Так что я делаю большинство таких классов, как это:

struct Whatever {
  Whatever();
  ~Whatever();
  private:
  struct Detail;
  std::unique_ptr<Detail> detail;
};

Итак, теперь у меня есть частная структура реализации, и, поскольку я использовал std::unique_ptr, мой класс верхнего уровня не подлежит копированию бесплатно. Исходящие из этого ошибки ссылок понятны, потому что они говорят о том, что вы не можете скопировать std::unique_ptr. Для меня это все преимущества boost:: noncopyable и частной реализации, объединенной в одну.

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

Недостаток, по словам Скотта Мейерса, имя "ненатруальное", если вам нужно найти его.

Другие вопросы по тегам