Как я могу расширить сгенерированный компилятором конструктор копирования - PullRequest
18 голосов
/ 31 января 2011

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

class xyz;
class C
{
    ...
    int a, b, c; 
    std::set<int> mySet;
    xyz *some_private_ptr;
};

Предположим, что some_private_ptr следует копировать только при определенных условиях. Для других условий его следует сбросить до NULL при копировании. Поэтому я должен написать конструктор копирования, например:

C::C(const C &other) : 
     a(other.a), 
     b(other.b), 
     c(other.c), 
    mySet(other.mySet)
{      
   if(CanCopy(other.some_private_ptr)) // matches condition
      some_private_ptr = other.some_private_ptr;
   else
      some_private_ptr = NULL;
}

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

C::C(const C &other) :
   C::default_copy(other)
{      
   if(CanCopy(other.some_private_ptr)) // matches condition
      some_private_ptr = other.some_private_ptr;
   else
      some_private_ptr = NULL;
}

Это сделает мой код более безопасным и простым в обслуживании. К сожалению, я не знаю такой возможности. Есть ли?

Ответы [ 5 ]

18 голосов
/ 31 января 2011

Самый простой способ - ввести базовый класс:

class xyz;

struct CDetail {
  //...
  int a, b, c; 
  std::set<int> mySet;
  xyz *some_private_ptr;
};

struct C : private CDetail {
  C(C const &other)
  : CDetail(other)
  {
    if (!CanCopy(other.some_private_ptr))
      some_private_ptr = 0;
    // opposite case already handled
  }
};

Это в некоторой степени злоупотребление наследованием, но преимущества перед вложенным классом «impl» заключаются в следующем: 1) вы можете получить доступ к каждому члену как «name», а не «data.name» (сокращая изменения кода при рефакторинге) и 2) (хотя только иногда желательно) вы можете «продвигать» отдельных участников в защищенные или публичные , не затрагивая других участников :

struct C : private CDetail {
protected:
  using CDetail::a;
};

struct D : C {
  void f() {
    cout << a;
  }
};

int main() {
  D d;
  d.f();  // D can access 'a'
  cout << d.a;  // main cannot access 'a'
  return 0;
}
16 голосов
/ 31 января 2011

в тот момент, когда вы определяете свою собственную копию ctor, компилятор не потрудится сгенерировать ее для вас.К сожалению, это означает, что вы должны делать всю работу по ногам самостоятельно!Вы можете сгруппировать членов в некую структуру impl_ в вашем классе, а затем использовать для этого копию ctor.

например:

class xyz;
class C
{
  struct impl_
  {
    int a, b, c; 
    std::set<int> mySet;
    xyz *some_private_ptr;
  };

  impl_ data;
};

теперь в вашем экземпляре ctor

C::C(const C &other) : data(other.data)
{
 // specific stuff...      
}
12 голосов
/ 31 января 2011

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

Вам необходимо разработать класс, который управляет ресурсом, который копируется только при определенных условиях. На самом деле вы еще не поняли, на чем основаны эти условия и почему они существуют (это очень странный способ «копировать» данные, вы уверены, что это лучший маршрут?), Но это было бы что-то как это:

// pointer to your condition member (not sure if this is even needed,
// is this condition knowable via the pointer alone? you get the point)
template <typename T, typename D, class Tag = void>
class copy_conditional_ptr
{
public:
    copy_conditional_ptr(bool (D::*condition)(T*) const, T* value = 0) :
    mCondition(condition),
    mValue(value)
    {}

    // here's where the unique copy-semantics go
    copy_conditional_ptr(const copy_conditional_ptr& other) :
    mCondition(other.mCondition),
    mValue(do_copy(other.mValue) ? other.mValue : 0)
    {}

    // other stuff for a smart pointer,
    // copy-and-swap, etc...

protected:
    // protected because it's meant to be a base class
    ~copy_conditional_ptr()
    {
        // whatever
    }

private:
    bool do_copy(T* value) const
    {
        const D& self = static_cast<const D&>(*this);
        return (self.*mCondition)(other.value);
    }

    bool (D::*mCondition)(T*) const;
    T* mValue;
};

Тогда вы используете это так:

class xyz;

class C : private copy_conditional_ptr<xyz, C>
{
public:
    C() :
    /* others, */
    copy_conditional_ptr(&C::CanCopy)
    {}

private:
    int a, b, c; 
    std::set<int> mySet;
};

И пусть управление будет автоматическим для остальной части класса. Тег, чтобы вы могли иметь несколько в одном классе:

class C : private copy_conditional_ptr<xyz, C, struct C_first>,
            private copy_conditional_ptr<xyz, C, struct C_second>
{
    // ...
};
0 голосов
/ 08 марта 2017

Вы можете поместить своего специального члена в базовый класс, например:

class xyz;

class SpecialCopyXYZ
{
public:
    SpecialCopyXYZ() = default;
    SpecialCopyXYZ(const SpecialCopyXYZ& rhs)
    {
       if (CanCopy(other.some_private_ptr)) {
          some_private_ptr = other.some_private_ptr;
       } else {
          some_private_ptr = nullptr;
       }
    }

    // SpecialCopyXYZ& operator=(const SpecialCopyXYZ& rhs)

protected:
    xyz *some_private_ptr = nullptr;
};


class C : private SpecialCopyXYZ
{
public:
    C(const C &other) = default;
private:
    int a, b, c; 
    std::set<int> mySet;
};

Если для SpecialCopyXYZ нужны данные C, вы можете использовать CRTP и downcast.

0 голосов
/ 31 августа 2011

Я бы сказал, создать умный указатель, который обрабатывает копирование, а затем использовать его в качестве члена вашего класса.Эти коды могут дать вам представление:

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

struct ABC{
    int a;
    ABC() : a(0)    {   printf("Default Constructor Called %d\n", a);   };

    ABC(ABC  & other )  
    {
        a=other.a;
        printf("Copy constructor Called %d \n" , a ) ;
    };
};

struct ABCDaddy{
    ABC abcchild;
};

Вы можете сделать следующие тесты:

printf("\n\nTest two, where ABC is a member of another structure\n" );
ABCDaddy aD;
aD.abcchild.a=2;

printf( "\n Test: ABCDaddy bD=aD;  \n" );
ABCDaddy bD=aD; // Does call the copy constructor of the members of the structure ABCDaddy ( ie. the copy constructor of ABC is  called)

printf( "\n Test: ABCDaddy cD(aD); \n" );
ABCDaddy cD(aD);    // Does call the copy constructor of the members of the structure ABCDaddy ( ie. the copy constructor of ABC is  called)

printf( "\n Test: ABCDaddy eD; eD=aD;  \n" );
ABCDaddy eD;
eD=aD;          // Does NOT call the copy constructor of the members of the structure ABCDaddy ( ie. the copy constructor of ABC is not called)

Вывод:

Default Constructor Called 0

Test: ABCDaddy bD=aD;
Copy constructor Called 2

Test: ABCDaddy cD(aD);
Copy constructor Called 2

Test: ABCDaddy eD; eD=aD;
Default Constructor Called 0

Наслаждаться.

...