Вызов конструктора копии по умолчанию из встроенного конструктора копирования

Мне нужно написать конструктор копирования, который глубоко копирует содержимое std::shared_ptr. Тем не менее, в классе также имеется группа переменных int a, b, c, d, e;. Есть ли способ генерировать код конструктора копии по умолчанию (или вызвать конструктор копии по умолчанию) внутри моего нового перегруженного.

Вот фрагмент кода с комментарием, который, надеюсь, прояснит проблему.

class Foo {
public:
     Foo() {}
     Foo(Foo const & other);
     ...
private:
     int a, b, c, d, e;
     std::shared_ptr<Bla> p;
};

Foo::Foo(Foo const & other) {
    p.reset(new Bla(*other.p));

    // Can I avoid having to write the default copy constructor code below
    a = other.a;
    b = other.b;
    c = other.c;
    d = other.d;
    e = other.e;
}

Ответ 1

Здесь код вопроса, как я & rsquo; m, пишущий это:

class Foo {
public:
     Foo() {}
     Foo(Foo const & other);
     ...
private:
     int a, b, c, d, e;
     std::shared_ptr<Bla> p;
};

Foo::Foo(Foo const & other) {
    p.reset(new Bla(other.p));

    // Can I avoid having to write the default copy constructor code below
    a = other.a;
    b = other.b;
    c = other.c;
    d = other.d;
    e = other.e;
}

Приведенный выше код, скорее всего, неправильный, потому что

  • конструктор по умолчанию оставляет a, b, c, d и e uninitialized и

  • код не отвечает за копирование присваивания, а

  • выражение new Bla(other.p) требует, чтобы Bla имел конструктор, принимающий a std::shared_ptr<Bla>, что крайне маловероятно.

С std::shared_ptr это должен быть код С++ 11, чтобы быть формально корректным по языку. Тем не менее, я считаю, что это просто код, который использует то, что доступно с вашим компилятором. И поэтому я считаю, что соответствующий стандарт С++ - это С++ 98 с техническими исправлениями поправки С++ 03.

Вы можете легко использовать встроенную (сгенерированную) инициализацию копии даже в С++ 98, например

namespace detail {
    struct AutoClonedBla {
        std::shared_ptr<Bla> p;

        AutoClonedBla( Bla* pNew ): p( pNew ) {}

        AutoClonedBla( AutoClonedBla const& other )
            : p( new Bla( *other.p ) )
        {}

        void swap( AutoClonedBla& other )
        {
            using std::swap;
            swap( p, other.p );
        }

        AutoClonedBla& operator=( AutoClonedBla other )
        {
            other.swap( *this );
            return *this;
        }
    };
}

class Foo {
public:
     Foo(): a(), b(), c(), d(), e(), autoP( new Bla ) {}
     // Copy constructor generated by compiler, OK.

private:
     int                      a, b, c, d, e;
     detail::AutoClonedBla    autoP;
};

Обратите внимание, что этот код правильно инициализируется в конструкторе по умолчанию, он берет на себя ответственность за присвоение копии (используя для этого обменивание идиомы) и не требует специального smart-pointer-aware Bla, но вместо этого просто использует обычный конструктор копирования Bla для копирования.

Ответ 2

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

В §12.8.4 стандарта указано, что:

Если определение класса явно не объявляет конструктор копирования, объявляется неявно.

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

Ответ 3

Было бы легче написать вариацию на shared_ptr, в которую встроено глубокое копирование. Таким образом, вам не нужно писать конструктор копирования для вашего основного класса; просто для этого специального типа deep_copy_shared_ptr. Ваш deep_copy_shared_ptr будет иметь конструктор копирования, и он сохранит сам shared_ptr. Он может даже иметь неявное преобразование в shared_ptr, чтобы сделать его немного проще в использовании.

Ответ 4

Насколько мне известно, но то, что вы можете (и должны) делать, это использовать список инициализаторов, так или иначе:

Foo::Foo(Foo const & other)
    : a(other.a), b(other.b), c(other.c), 
      d(other.d), e(other.e), p(new Bla(other.p))
{}

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

И, кстати, комментарий Керрека прав. Зачем вам нужен shared_ptr, если вы сделаете глубокую копию в любом случае. В этом случае a unique_ptr может быть более уместным. Кроме того, преимущества shared_ptr - это не общее решение, не требующее особого внимания, и вы всегда должны думать, если вам нужен умный указатель и какой тип интеллектуального указателя наиболее подходит.

Ответ 5

Это невозможно. Это либо вы пишете собственный конструктор копирования (полностью по своему усмотрению), либо компилятор записывает его для вас.

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

Кроме того, я не совсем уверен, почему вы используете shared_ptr<>. Точка a shared_ptr<> должна позволить нескольким указателям безопасно указывать на один и тот же объект. Но вы не делитесь плацдармом, вы глубоко копируете его. Возможно, вы должны использовать необработанный указатель и освободить его в деструкторе. Или, еще лучше, замените shared_ptr<> на clone_ptr, а затем устраните конструктор копирования, назначение копии и деструктор вообще.