2012-05-04 26 views
5

Tôi đã nhiều lớp mà cần clone hàm sau được xác định:C++ Cloneable mixin

struct Base 
{ 
    virtual Base * clone() const = 0; 
}; 

struct A : public Base 
{ 
    Base * clone() const { 
     return new A(*this); 
    } 
}; 

struct B : public Base 
{ 
    Base * clone() const { 
     return new B(*this); 
    } 
}; 

struct X : public Base2 
{ 
    Base2 * clone() const { 
     return new X(*this); 
    } 
}; 

Tôi cố gắng để làm điều này với một mixin Cloneable để tránh mã dư thừa này:

template <typename BASE, typename TYPE> 
class CloneableMixin 
{ 
public: 
    BASE*clone() const { 
    return new TYPE(dynamic_cast<const TYPE &>(*this)); 
    } 
}; 

struct A : public Base, public CloneableMixin<Base, A> 
{ 
}; 

Tuy nhiên, điều này không hiệu quả, bởi vì trong new TYPE(*this) từ CloneableMixin, *this thuộc loại CloneableMixin<BASE, TYPE>.

Cập nhật: các CloneableMixin có thể dynamic_cast cho đúng loại. Nhưng bây giờ tôi có một vấn đề khác: CloneableMixin::clone không ghi đè thành công Base::clone và do đó trình biên dịch báo cáo A là một kiểu trừu tượng.

Có thể sử dụng thông minh virtual cho phép thừa kế CloneableMixin::clone để ghi đè Base::clone? Có một số vĩ mô tôi nên sử dụng cho điều này?

Bạn có biết cách nào đó xung quanh tất cả mã dự phòng này không?

+0

Bản sao có thể có của http://stackoverflow.com/questions/9422760/inheritance-in-curiously-recurring-template-pattern-polymorphic-copy-c – TemplateRex

+1

'Base :: clone' cần phải là' virtual' theo thứ tự cho '= 0' hợp lệ. – TemplateRex

+0

@rhalbersma Tôi không chắc nó có giống nhau hay không. Không phải tất cả các lớp cloneable của tôi đều có cùng loại cơ sở (ví dụ: 'Base',' Base2'). – user

Trả lời

0

Trong quá trình khởi tạo mixin Cloneable của bạn, lớp dẫn xuất vẫn còn trong loại không đầy đủ. Bạn có thể cố gắng bổ sung thêm các Leval ngôn về mình như thế này:

template 
< 
    typename Derived 
> 
class Cloneable 
:  
    private CloneableBase 
{ 
public: 
    Derived* clone() const 
    { 
     return static_cast<Derived*>(this->do_clone()); 
    } 

private: 
    virtual Cloneable* do_clone() const 
    { 
     return new Derived(static_cast<const Derived&>(*this)); 
    } 
}; 

class CloneableBase 
{ 
public: 
    CloneableBase* clone() const 
    { 
     return do_clone(); 
    } 

private: 
    virtual CloneableBase* do_clone() const=0; 
}; 

class MyClass: public Cloneable<MyClass>; 
+0

Giải pháp này không phù hợp với tôi. Trong trường hợp của tôi, A <- B <- C <- D. A là lớp cơ sở (trừu tượng), B là một lớp trừu tượng.C và D là các lớp cụ thể. Một khai báo 'virtual A * clone() const = 0;' và B tương tự. Khi tôi cho C <- Cloneable , GCC nói 'tham chiếu không xác định đối với 'vtable cho C''. Mã hoạt động tốt nếu tôi thực hiện nó bằng tay (không sử dụng mẫu). [GCC 4.5.3, Cygwin] –

+0

@ AsukaKenji-SiuChingPong- Bạn có thể cung cấp ví dụ về LiveWorkspace để hiển thị mã của bạn không? – TemplateRex

4

một số sử dụng thông minh của thừa kế ảo có thể cho phép CloneableMixin :: clone để ghi đè lên cơ sở :: bản sao?

bạn CloneableMixin<Base,Derived> không thể ghi đè lên bất kỳ phương pháp Base - hoặc polymorphically hoặc bằng cách lẩn trốn - vì CloneableMixin<Base,Derived> là không bắt nguồn từ Base.

Mặt khác, nếu CloneableMixin<Base,Derived> có nguồn gốc từ Base bạn sẽ không còn có bất kỳ nhu cầu cho nó trở thành một mixin, bởi vì -

class Derived : public CloneableMixin<Base,Derived> {....}; 

sẽ kế thừa Base.

Vì vậy, đối với nhu cầu của ví dụ của bạn là giải pháp được minh họa ở đây là đủ:

#include <iostream> 

// cloner v1.0 
template <class Base, class Derived> 
struct cloner : Base 
{ 
    Base *clone() const override { 
     return new Derived(dynamic_cast<const Derived &>(*this)); 
    } 
    ~cloner() override {}; 
}; 

struct Base 
{ 
    virtual Base * clone() const = 0; 
    Base() { 
     std::cout << "Base()" << std::endl; 
    } 
    virtual ~Base() { 
     std::cout << "~Base()" << std::endl; 
    } 
}; 


struct A : cloner<Base,A> 
{ 
    A() { 
     std::cout << "A()" << std::endl; 
    } 
    ~A() override { 
     std::cout << "~A()" << std::endl; 
    } 
}; 

int main() 
{ 
    A a; 
    Base * pb = a.clone(); 
    delete pb; 
} 

(Nếu bạn đang biên soạn theo tiêu chuẩn C++ 03 chứ không phải là C++ 11, sau đó bạn có thể chỉ cần xóa sự xuất hiện của từ khóa override.)

Giải pháp này sẽ phá vỡ một số phân cấp lớp thực tế hơn, ví dụtrong minh họa này của Template Method Pattern:

#include <iostream> 
#include <memory> 

using namespace std; 

// cloner v1.0 
template<class B, class D> 
struct cloner : B 
{ 
    B *clone() const override { 
     return new D(dynamic_cast<D const&>(*this)); 
    } 
    ~cloner() override {}  
}; 

/* Abstract base class `abstract` keeps the state for all derivatives 
    and has some pure virtual methods. It has some non-default 
    constructors. 
*/ 
struct abstract 
{ 
    virtual ~abstract() { 
     cout << "~abstract()" << endl; 
    } 
    int get_state() const { 
     return _state; 
    } 
    void run() { 
     cout << "abstract::run()" << endl; 
     a_root_method(); 
     another_root_method(); 
    } 
    virtual void a_root_method() = 0; 
    virtual void another_root_method() = 0; 
    virtual abstract * clone() const = 0; 

protected: 

    abstract() 
    : _state(0) { 
     cout << "abstract(): state = " << get_state() << endl; 
    } 
    explicit abstract(int state) : _state(state) { 
     cout << "abstract(" << state << ") : state = " 
     << get_state() << endl; 
    } 
    int _state; 
}; 

/* Concrete class `concrete` inherits `abstract` 
    and implements the pure virtual methods. 
    It echoes the constructors of `abstract`. Since `concrete` 
    is concrete, it requires cloneability. 
*/ 
struct concrete : cloner<abstract,concrete> 
{ 
    concrete() { 
     cout << "concrete(): state = " << get_state() << endl; 
    } 
    explicit concrete(int state) : abstract(state) { //<- Barf! 
     cout << "concrete(" << state << ") : state = " 
      << get_state() << endl; 
    } 
    ~concrete() override { 
     cout << "~concrete()" << endl; 
    } 
    void a_root_method() override { 
     ++_state; 
     cout << "concrete::a_root_method() : state = " 
      << get_state() << endl; 
    } 
    void another_root_method() override { 
     --_state; 
     cout << "concrete::another_root_method() : state = " 
      << get_state() << endl; 
    }  
}; 

int main(int argc, char **argv) 
{ 
    concrete c1; 
    unique_ptr<abstract> pr(new concrete(c1)); 
    pr->a_root_method(); 
    pr->another_root_method(); 
    unique_ptr<abstract> pr1(pr->clone()); 
    pr1->a_root_method(); 
    return 0; 
} 

Khi chúng tôi cố gắng để xây dựng này, trình biên dịch sẽ đưa ra một lỗi ở khởi abstract(state) trong constuctor của concrete (tại Barf! bình luận), nói:

error: type 'abstract' is not a direct or virtual base of 'concrete' 

hoặc các từ có hiệu lực đó. Thật vậy, cơ sở trực tiếp của concrete không phải là abstract nhưng cloner<abstract,concrete>. Tuy nhiên, chúng ta không thể viết lại các nhà xây dựng như:

/*Plan B*/ explicit concrete(int state) : cloner<abstract,concrete>(state){....} 

Bởi vì có được không có constructor như

cloner<abstract,concrete>::cloner<abstract,concrete>(int) 

Nhưng của trình biên dịch chẩn đoán cho thấy một sửa chữa. Điều này là ảo kế thừa có thể hữu ích. Chúng ta cần abstract để trở thành một cơ sở ảo của concrete, mà nghĩa một cách hiệu quả "một cơ sở trực tiếp danh dự của concrete", và chúng ta có thể đạt được điều đó chỉ bằng cách làm cho B cơ sở ảo của cloner<B,D>:

// cloner v1.1 
template<class B, class D> 
struct cloner : virtual B 
{ 
    B *clone() const override { 
     return new D(dynamic_cast<D const&>(*this)); 
    } 
    ~cloner() override {}  
}; 

Cùng với đó, chúng tôi có một xây dựng sạch và đầu ra:

abstract(): state = 0 
concrete(): state = 0 
concrete::a_root_method() : state = 1 
concrete::another_root_method() : state = 0 
concrete::a_root_method() : state = 1 
~concrete() 
~abstract() 
~concrete() 
~abstract() 
~concrete() 
~abstract() 

có nhiều lý do tốt để cảnh giác với o thừa kế ảo n nguyên tắc và để dự trữ việc sử dụng ít nhất cho các trường hợp trong đó có kiến ​​trúc lý do - không phải để giải quyết sự cố, vì chúng tôi đã sử dụng nó ngay bây giờ.

Nếu chúng ta thích làm mà không thừa kế ảo cho vấn đề này, sau đó chúng tôi phải bằng cách nào đó đảm bảo rằng có một constructor của cloner<B,D> rằng vang bất kỳ constuctor của B, cho tùy ý B. Sau đó, bất kỳ phương thức khởi tạo tương ứng nào của D sẽ có thể khởi tạo cơ sở trực tiếp cloner<B,D> bất kỳ đối số nào.

Đây là một Pipedream cho C++ 03, nhưng với sự kỳ diệu của mẫu variadic tham số trong C++ 11 nó rất dễ dàng:

// cloner v1.2 
template<class B, class D> 
struct cloner : B 
{ 
    B *clone() const override { 
     return new D(dynamic_cast<D const&>(*this)); 
    } 
    ~cloner() override {} 
    // "All purpose constructor" 
    template<typename... Args> 
    explicit cloner(Args... args) 
    : B(args...){} 
}; 

Với điều này, chúng ta có thể viết lại constructor concrete như /*Plan B*/ và một lần nữa chúng tôi có bản dựng và thực thi chính xác.

+0

P.S. Biên soạn với GCC 4.7.2 và Clang 3.2 –

Các vấn đề liên quan