Unique_ptr olan bir sınıf için yapıcıyı kopyala


105

unique_ptrÜye değişkeni olan bir sınıf için bir kopya oluşturucusunu nasıl uygularım ? Ben sadece C ++ 11'i düşünüyorum.


9
Peki, kopya oluşturucunun ne yapmasını istiyorsunuz?
Nicol Bolas

Unique_ptr'nin kopyalanamaz olduğunu okudum. Bu beni bir unique_ptr üye değişkeni olan bir sınıf kullanmak nasıl meraklandırıyor std::vector.
kodeks

2
@AbhijitKadam Unique_ptr içeriğinin derin bir kopyasını oluşturabilirsiniz. Aslında, bu genellikle yapılması gereken en mantıklı şeydir.
Kübik

2
Lütfen muhtemelen yanlış soruyu sorduğunuzu unutmayın. Muhtemelen sınıfınız için a içeren bir kopya oluşturucu unique_ptristemiyorsunuzdur, amacınız verileri bir std::vector. Öte yandan, C ++ 11 standardı otomatik olarak hareket oluşturucuları oluşturdu, bu yüzden belki bir kopya oluşturucu istiyorsunuz ...
Yakk - Adam Nevraumont

3
@codefx vektör öğelerinin kopyalanabilir olması gerekmez; bu sadece vektörün kopyalanamayacağı anlamına gelir.
MM

Yanıtlar:


81

Yana unique_ptrpaylaşılamaz ya içeriğini derin kopyalamak veya dönüştürmek için, size gereken unique_ptrbir etmek shared_ptr.

class A
{
   std::unique_ptr< int > up_;

public:
   A( int i ) : up_( new int( i ) ) {}
   A( const A& a ) : up_( new int( *a.up_ ) ) {}
};

int main()
{
   A a( 42 );
   A b = a;
}

NPE'den bahsedildiği gibi, copy-ctor yerine move-ctor kullanabilirsiniz, ancak bu, sınıfınızın farklı anlamlarına neden olur. Bir move-ctor, üyeyi aşağıdakiler aracılığıyla açıkça taşınabilir hale getirmelidir std::move:

A( A&& a ) : up_( std::move( a.up_ ) ) {}

Gerekli operatörlerin eksiksiz bir setine sahip olmak aynı zamanda

A& operator=( const A& a )
{
   up_.reset( new int( *a.up_ ) );
   return *this,
}

A& operator=( A&& a )
{
   up_ = std::move( a.up_ );
   return *this,
}

Sınıfınızı a'da kullanmak istiyorsanız std::vector, temelde vektörün bir nesnenin benzersiz sahibi olup olmayacağına karar vermelisiniz, bu durumda sınıfı taşınabilir, ancak kopyalanamaz hale getirmek yeterli olacaktır. Copy-ctor ve copy-assignment'ı dışarıda bırakırsanız, derleyici std :: vector'u yalnızca taşıma türleriyle nasıl kullanacağınız konusunda size yol gösterecektir.


4
Taşımacılardan bahsetmeye değer olabilir mi?
NPE

4
+1, ancak hareket yapıcı daha fazla vurgulanmalıdır. Bir yorumda OP, amacın nesneyi bir vektörde kullanmak olduğunu söylüyor. Bunun için, gerekli olan tek şey taşınma yapımı ve taşınma görevidir.
jogojapan

36
Bir uyarı olarak, yukarıdaki strateji gibi basit türler için çalışır int. A unique_ptr<Base>depolayan bir dosyanız varsa Derived, yukarıdakiler dilimleyecektir.
Yakk - Adam Nevraumont

5
Null için bir kontrol yoktur, bu nedenle bu nullptr ayrıştırmasına izin verir. Peki yaA( const A& a ) : up_( a.up_ ? new int( *a.up_ ) : nullptr) {}
Ryan Haining

1
@Aaron, polimorfik durumlarda, silici bir şekilde silinir veya anlamsız olur (silinecek türü biliyorsanız, neden yalnızca silici değiştirilsin?). Her durumda, evet, bu a value_ptr- unique_ptrplus silici / kopyalayıcı bilgisinin tasarımıdır .
Yakk - Adam Nevraumont

47

Bir kişinin unique_ptrbir sınıfta a'ya sahip olmasının olağan durumu, kalıtımı kullanabilmektir (aksi takdirde düz bir nesne çoğu zaman işe yarar, bkz. RAII). Bu durum için şimdiye kadar bu başlıkta uygun bir cevap yok .

İşte başlangıç ​​noktası:

struct Base
{
    //some stuff
};

struct Derived : public Base
{
    //some stuff
};

struct Foo
{
    std::unique_ptr<Base> ptr;  //points to Derived or some other derived class
};

... ve amaç, söylendiği gibi, Fookopyalanabilir kılmaktır.

Bunun için , türetilmiş sınıfın doğru bir şekilde kopyalanmasını sağlamak için içerilen göstericinin derin bir kopyasının yapılması gerekir.

Bu, aşağıdaki kodu ekleyerek gerçekleştirilebilir:

struct Base
{
    //some stuff

    auto clone() const { return std::unique_ptr<Base>(clone_impl()); }
protected:
    virtual Base* clone_impl() const = 0;
};

struct Derived : public Base
{
    //some stuff

protected:
    virtual Derived* clone_impl() const override { return new Derived(*this); };                                                 
};

struct Foo
{
    std::unique_ptr<Base> ptr;  //points to Derived or some other derived class

    //rule of five
    ~Foo() = default;
    Foo(Foo const& other) : ptr(other.ptr->clone()) {}
    Foo(Foo && other) = default;
    Foo& operator=(Foo const& other) { ptr = other.ptr->clone(); return *this; }
    Foo& operator=(Foo && other) = default;
};

Burada temelde iki şey var:

  • Birincisi Foo, copy yapıcısı silindiğinde örtük olarak silinen copy ve move yapıcılarının eklenmesidir unique_ptr. Hareket yapıcı basitçe eklenebilir = default... her zamanki hareket yapıcı edeceğini derleyici haberdar etmenin hangi değil (gibi bu eserler silinecek unique_ptrzaten bu durumda kullanılabilecek bir hamle yapıcısı vardır).

    Kopyalama yapıcısı için, kopya yapıcısı Fooolmadığından benzer bir mekanizma yoktur unique_ptr. Bu nedenle, yeni bir tane oluşturmak unique_ptr, onu orijinal pointee'nin bir kopyasıyla doldurmak ve kopyalanan sınıfın bir üyesi olarak kullanmak gerekir.

  • Miras söz konusu ise, orijinal pointee'nin kopyası dikkatlice yapılmalıdır. Bunun nedeni, std::unique_ptr<Base>(*ptr)yukarıdaki kod aracılığıyla basit bir kopyalama yapmanın dilimlemeye yol açmasıdır, yani türetilmiş parça eksikken yalnızca nesnenin temel bileşeni kopyalanır.

    Bundan kaçınmak için, kopyalama klon modeli aracılığıyla yapılmalıdır. Buradaki fikir, kopyalamayı temel sınıfta clone_impl()a döndüren sanal bir işlev aracılığıyla yapmaktır Base*. Bununla birlikte, türetilmiş sınıfta, bir döndürmek için kovaryans yoluyla genişletilir Derived*ve bu işaretçi türetilmiş sınıfın yeni oluşturulmuş bir kopyasına işaret eder. Temel sınıf daha sonra bu yeni nesneye temel sınıf işaretçisi aracılığıyla erişebilir Base*, onu a'ya sarabilir ve dışarıdan çağrılan unique_ptrgerçek clone()işlev aracılığıyla geri döndürebilir .


3
Bu kabul edilen cevap olmalıydı. Diğer herkes unique_ptr, doğrudan kapsama başka türlü yapacağı zaman neden işaret edilen bir nesneyi kopyalamak isteyeceğine dair ipucu vermeden, bu ileti dizisinde daireler çiziyor . Cevap??? Kalıtım .
Tanveer Badar

4
Somut türü çeşitli nedenlerle işaret ettiklerini bildiklerinde bile unique_ptr kullanılıyor olabilir: 1. Null yapılabilir olması gerekir. 2. Pointee çok büyük ve sınırlı yığın alanımız olabilir. Genellikle (1) ve (2) dolayısıyla vesilesiyle bir kudreti tercih birlikte gidecek unique_ptrüzerinde optionalnull türleri.
Ponkadoodle

3
Sivilce deyimi başka bir nedendir.
emsr

Ya bir temel sınıf soyut olmamalıysa? Saf tanımlayıcı olmadan bırakmak, türetilmiş olarak yeniden uygulamayı unutursanız çalışma zamanı hatalarına yol açabilir.
olek stolar

1
@ OleksijPlotnyc'kyj: evet, eğer clone_implin base'i uygularsanız , derleyici bunu türetilmiş sınıfta unutursanız size söylemez. Bununla birlikte, başka bir temel sınıf kullanabilir Cloneableve clone_implorada saf bir sanal uygulayabilirsiniz . Daha sonra, türetilmiş sınıfta unutursanız derleyici şikayet eder.
davidhigh

11

Derin kopyalar oluşturmak için bu yardımcıyı deneyin ve kaynak unique_ptr boş olduğunda başa çıkın.

    template< class T >
    std::unique_ptr<T> copy_unique(const std::unique_ptr<T>& source)
    {
        return source ? std::make_unique<T>(*source) : nullptr;
    }

Örneğin:

class My
{
    My( const My& rhs )
        : member( copy_unique(rhs.member) )
    {
    }

    // ... other methods

private:
    std::unique_ptr<SomeType> member;
};

2
Kaynak T'den türetilmiş bir şeye işaret ederse doğru bir şekilde kopyalanacak mı?
Roman Shapovalov

3
@RomanShapovalov Hayır, muhtemelen hayır, dilimleme yaparsınız. Bu durumda, çözüm muhtemelen T türünüze sanal bir benzersiz_ptr <T> clone () yöntemi eklemek ve T'den türetilen türlerde clone () yönteminin geçersiz kılmalarını sağlamak olacaktır. Clone yöntemi yeni bir örnek oluşturacaktır. türetilmiş tür ve bunu döndür.
Scott Langham

C ++ 'da benzersiz / kapsamlı işaretçiler yok mu veya yerleşik derin kopyalama işlevine sahip kitaplıkları yükseltiyor mu? Derin kopya davranışını istediğimizde, bu akıllı işaretçileri kullanan sınıflar için özel kopya oluşturucularımızı vb. Oluşturmak zorunda kalmamak güzel olurdu, ki bu genellikle böyledir. Merak ediyorum.
shadow_map

5

Daniel Frey kopyalama çözümünden bahsetti, ben unique_ptr'nin nasıl taşınacağından bahsedeceğim

#include <memory>
class A
{
  public:
    A() : a_(new int(33)) {}

    A(A &&data) : a_(std::move(data.a_))
    {
    }

    A& operator=(A &&data)
    {
      a_ = std::move(data.a_);
      return *this;
    }

  private:
    std::unique_ptr<int> a_;
};

Hareket oluşturucu ve taşıma ataması olarak adlandırılırlar

onları böyle kullanabilirsin

int main()
{
  A a;
  A b(std::move(a)); //this will call move constructor, transfer the resource of a to b

  A c;
  a = std::move(c); //this will call move assignment, transfer the resource of c to a

}

A ve c'yi std :: move ile sarmalamanız gerekir çünkü bir isimleri vardır std :: move derleyiciye değeri parametreler ne olursa olsun rvalue referansına dönüştürmesini söyler Teknik anlamda std :: move " std :: rvalue "

Taşındıktan sonra, unique_ptr'nin kaynağı başka bir unique_ptr'ye transfer edilir.

Rvalue referansını belgeleyen birçok konu vardır; bu, başlamak için oldukça kolay .

Düzenle :

Taşınan nesne geçerli ancak belirtilmemiş durumda kalacaktır .

C ++ primer 5, ch13 ayrıca nesnenin nasıl "taşınacağı" konusunda çok iyi bir açıklama verir


1
Peki a, bmove yapıcısında std :: move (a) çağrıldıktan sonra nesneye ne olur ? Tamamen geçersiz mi?
David Doria

3

Make_unique kullanmanızı öneririm

class A
{
   std::unique_ptr< int > up_;

public:
   A( int i ) : up_(std::make_unique<int>(i)) {}
   A( const A& a ) : up_(std::make_unique<int>(*a.up_)) {};

int main()
{
   A a( 42 );
   A b = a;
}

-1

unique_ptr kopyalanamaz, sadece taşınabilir.

Bu, ikinci örneğinizde yalnızca taşınabilir olan ve kopyalanamayan Test'i doğrudan etkileyecektir.

Aslında, unique_ptrsizi büyük bir hatadan koruyacak şekilde kullanmanız iyidir .

Örneğin, ilk kodunuzla ilgili ana sorun, işaretçinin asla silinmemesidir ki bu gerçekten çok kötüdür. Söyle, bunu şu şekilde düzelteceksin:

class Test
{
    int* ptr; // writing this in one line is meh, not sure if even standard C++

    Test() : ptr(new int(10)) {}
    ~Test() {delete ptr;}
};

int main()
{       
     Test o;
     Test t = o;
}

Bu da kötü. Kopyalarsan ne olur Test? Aynı adresi gösteren bir işaretçiye sahip iki sınıf olacaktır.

Biri Testyok edildiğinde, aynı zamanda işaretçiyi de yok eder. İkinciniz Testyok edildiğinde, işaretçinin arkasındaki belleği de kaldırmaya çalışacaktır. Ancak zaten silindi ve bazı kötü bellek erişim çalışma zamanı hatası alacağız (veya şanssızsak tanımlanmamış davranış).

Dolayısıyla, doğru yol, ya kopya yapıcıyı uygulamak ve atama işlecini kopyalamaktır, böylece davranış açık olur ve bir kopya oluşturabiliriz.

unique_ptrburada bizden çok ileride. Anlamsal anlamı vardır: " Ben öyleyim unique, bu yüzden beni öylece kopyalayamazsınız. " Bu nedenle, elimizdeki operatörleri şimdi uygulama hatasından bizi engelliyor.

Özel davranış için kopyalama yapıcı ve kopyalama atama işleci tanımlayabilirsiniz ve kodunuz çalışacaktır. Ama sen, haklı olarak öyle (!), Bunu yapmaya zorlandın.

Hikayenin ahlaki: her zaman unique_ptrbu tür durumlarda kullanın.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.