2017-01-18 8 views
3

verkapselt Ich habe eine Vorlage Klasse derived_object<T>, die sehr ähnlich zu std::unique_ptr ist. Es hat die zusätzliche Eigenschaft, dass Sie eine tiefe Kopie erstellen können, obwohl T nur eine Basisklasse des tatsächlichen Objekts ist. Die Technik stammt von boost::any. Hier ist der (leider lang) Inhalt der Datei derived_object.h:Move Semantic unbeabsichtigt ersetzt durch Kopie Konstruktor in Klasse, die Zeiger

#pragma once 
#include <type_traits> 
#include <algorithm> 

template<class Base> 
class derived_object 
{ 
public: 
    derived_object() 
     : content(nullptr) 
    {} 

    template<typename Derived> 
    derived_object(Derived* ptr) 
     : content(new holder<Derived>(ptr)) 
    { 
     static_assert(std::is_copy_constructible<Derived>::value, "type of pointer must have copy constructor"); 
     static_assert(std::is_base_of<Base, Derived>::value, "type of pointer must be derived from base type"); 
    } 

    derived_object(derived_object const& other); // left unimplemented for testing 
//  : content(other.content ? other.content->clone() : nullptr) 
// {} 

    derived_object& operator=(derived_object const& rhs); // left unimplemented for testing 
// { 
//  derived_object<Base>(rhs).swap(*this); 
//  return *this; 
// } 

    derived_object(derived_object&& other) noexcept 
     : content(other.content) 
    { 
     other.content = nullptr; 
    } 

    derived_object& operator=(derived_object&& rhs) noexcept 
    { 
     rhs.swap(*this); 
     derived_object<Base>().swap(rhs); 
     return *this; 
    } 

    ~derived_object() 
    { 
     delete content; 
    } 

    Base* operator->() const 
    { 
     return content->operator Base*(); 
    } 

    Base* get() const 
    { 
     return content->operator Base*(); 
    } 

    derived_object& swap(derived_object& rhs) noexcept 
    { 
     std::swap(content, rhs.content); 
     return *this; 
    } 

private: 
    class placeholder 
    { 
    public: 
     virtual ~placeholder() {} 
     virtual operator Base*() const = 0; 
     virtual placeholder* clone() const = 0; 
    }; 

    template<typename Derived> 
    class holder : public placeholder 
    { 
     Derived* held; 
    public: 
     holder(Derived* der_ptr) 
      : held(der_ptr) 
     { } 
     placeholder* clone() const override 
     { 
      return new holder(new Derived(*held)); 
     } 
     ~holder() override 
     { 
      delete held; 
     } 
     operator Base*() const override { return held; } 
    }; 

    placeholder* content; 
}; 

Dieses schönen im Allgemeinen funktioniert. Aber in einem speziellen Fall, in dem ich die Verschiebungssemantik anstelle des Kopierens verwenden möchte, schlägt mein Programm fehl. Zu Testzwecken habe ich das Kopieren in derived_object deaktiviert, indem ich die entsprechenden Methoden deklariert, aber nicht definiert habe.

Hier sind die Inhalte von classes.h

#pragma once 
#include "derived_object.h" 

class B1 
{}; 
class D1 : public B1 
{}; 


class B2 
{}; 
class D2 : public B2 
{ 
public: 
    D2(derived_object<B1> member); 
    derived_object<B1> member; 
}; 

classes.cpp

#include "classes.h" 

D2::D2(derived_object<B1> m) 
: member(std::move(m)) 
{} 

und main.cpp

#include "classes.h" 

int main() 
{ 
    derived_object<B2> e(new D2(new D1())); 
} 

Kompilieren mit Clang (Version 3.8.0-2ubuntu4) über

clang++ -std=c++14 -o main.cpp.o -c main.cpp 
clang++ -std=c++14 -o classes.cpp.o -c classes.cpp 
clang++ main.cpp.o classes.cpp.o -o output 

gibt den Linker-Fehler main.cpp:(.text._ZN2D2C2ERKS_[_ZN2D2C2ERKS_]+0x19): undefined reference to `derived_object<B1>::derived_object(derived_object<B1> const&)'.

Aber es sollte kein Kopieren von abgeleiteten_Objekt, ich bewegte mich immer oder verwendet den Konstruktor derived_object(Derived* ptr).

Und seltsam, wenn ich den D2 Konstruktor inline deklarieren oder andere kleine Änderungen vornehmen, tritt der Fehler nicht auf.
Warum möchte der Compiler mein Objekt kopieren, obwohl das Verschieben möglich zu sein scheint?
Wie kann ich derived_object.h ändern (abgesehen von der Änderung seiner Methodensignaturen)?

Antwort

1

ändern

derived_object& operator=(derived_object const& rhs); 
derived_object(derived_object const& other); 

zu

derived_object& operator=(derived_object const& rhs)=delete; 
derived_object(derived_object const& rhs)=delete; 

dies werden Sie Fehler bei der Kompilierung-Zeit geben, keine Zeit verknüpfen, wenn sie verwendet werden.

When I do this (live example), erhalte ich einen klaren Fehler hier:

static_assert(std::is_copy_constructible<Derived>::value, "type of pointer must have copy constructor"); 

Voll Fehler:

main.cpp:16:9: error: static_assert failed "type of pointer must have copy constructor" 
     static_assert(std::is_copy_constructible<Derived>::value, "type of pointer must have copy constructor"); 
     ^   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
main.cpp:109:24: note: in instantiation of function template specialization 'derived_object<B2>::derived_object<D2>' requested here 
    derived_object<B2> e(new D2(new D1())); 
        ^
main.cpp:76:35: error: call to implicitly-deleted copy constructor of 'D2' 
     return new holder(new Derived(*held)); 
          ^  ~~~~~ 
main.cpp:71:9: note: in instantiation of member function 'derived_object<B2>::holder<D2>::clone' requested here 
     holder(Derived* der_ptr) 
     ^

, die mich dann hier Punkte:

 placeholder* clone() const override 
     { 
      return new holder(new Derived(*held)); 
     } 

Sie werden Sie bemerken rae Kopieren*held hier und wha t mehr Sie beabsichtigen zu kopieren *held.

Die Klasse enthält, dass clone hier erstellt:

content(new holder<Derived>(ptr)) 

, die dann den Kopierkonstruktor von D2 aufruft, die implizit Kopien member, die derived_object vom Typ ist.

Wenn Sie den Kopierkonstruktor von derived_object umfassen, aber es unimplemented verlassen, dass implizite Kopie derived_object innerhalb des erzeugten Copykonstruktor von D2 bewirkt, dass Ihr Link Fehler. Es wird in derived_object<D2> verwendet, wenn es den holder<D2>-Typ erstellt.

Virtuelle Funktionen, auch wenn sie nie aufgerufen werden, werden implementiert und kompiliert.

Wir setzen dann die beiden Methoden:

derived_object(derived_object const& other): 
    content(other.content?other.content->clone():nullptr) 
{} 
derived_object& operator=(derived_object const& rhs) { 
    auto tmp = rhs; 
    return (*this)=std::move(tmp); 
} 

and everything builds fine (live example).

Beachten Sie, dass die Tiefenkopie hier nicht vom Basistyp kopiert wird. Wir haben Typ gelöschte Klonen des tatsächlichen abgeleiteten Typs, nicht der Basistyp.

Alternativ, um zu bestätigen, dass keine dummen Kopien erstellt wurden, lassen wir beide die Kopie zuweisen und ctor nicht implementiert, und wir entfernen clone. This also compiles (live example).

+0

Es ist in der Tat die Klon-Methode, obwohl nie verwendet. Ich habe es auskommentiert, danke. Es gab andere Fälle, in denen der Linker nicht scheiterte, wahrscheinlich wegen cleverer Optimierungen. (?) Ganz gleich. – Frank

0

Löschen des Copykonstruktor (mit = delete Syntax) statt so dass es nicht definiert, sollten Sie einen entsprechenden Hinweis aus dem Compiler geben, die Ihre Aufmerksamkeit auf die Linie ziehen sollte:

return new holder(new Derived(*held));

Es gibt eine offensichtliche Kopie einer benannten Mitgliedsvariablen.

Verwandte Themen