2009-08-04 33 views
73

Tôi có lớp B với một bộ các hàm tạo và một toán tử gán.Làm thế nào để sử dụng các hàm tạo của lớp cơ sở và toán tử gán trong C++?

class B 
{ 
public: 
    B(); 
    B(const string & s); 
    B(const B & b){(*this) = b;}; 
    B & operator= (const B & b); 
private: 
    virtual void foo(); 
    // and other private member variables and functions 
} 

Tôi muốn tạo lớp kế thừa D sẽ chỉ ghi đè hàm foo() và không cần thay đổi khác.

Nhưng tôi muốn D để có cùng một tập hợp nhà thầu, bao gồm constructor sao chép và toán tử gán như B:

D(const D & d){(*this) = d;}; 
    D & operator= (const D & d); 

Tôi có phải viết lại tất cả trong số họ trong D, hoặc là có một cách để sử dụng B của nhà thầu và nhà điều hành? Tôi đặc biệt muốn tránh viết lại toán tử gán vì nó phải truy cập tất cả các biến thành viên riêng của B.

+0

Nếu bạn chỉ muốn ghi đè phương thức 'foo', bạn có thể sử dụng' using B :: operator =; 'để kế thừa toán tử gán, nhưng sao chép và di chuyển các hàm tạo không thể được kế thừa: https://stackoverflow.com/q/49045026/5447906 –

Trả lời

93

Bạn rõ ràng có thể gọi nhà thầu và các toán tử gán:

class Base { 
//... 
public: 
    Base(const Base&) { /*...*/ } 
    Base& operator=(const Base&) { /*...*/ } 
}; 

class Derived : public Base 
{ 
    int additional_; 
public: 
    Derived(const Derived& d) 
     : Base(d) // dispatch to base copy constructor 
     , additional_(d.additional_) 
    { 
    } 

    Derived& operator=(const Derived& d) 
    { 
     Base::operator=(d); 
     additional_ = d.additional_; 
     return *this; 
    } 
}; 

Điều thú vị là làm việc này ngay cả khi bạn không xác định rõ ràng các chức năng này (sau đó nó sử dụng các chức năng biên dịch tạo ra).

class ImplicitBase { 
    int value_; 
    // No operator=() defined 
}; 

class Derived : public ImplicitBase { 
    const char* name_; 
public: 
    Derived& operator=(const Derived& d) 
    { 
     ImplicitBase::operator=(d); // Call compiler generated operator= 
     name_ = strdup(d.name_); 
     return *this; 
    } 
}; 
+0

Điều này có nghĩa là gì? 'Base (const Base &)' – qed

+1

@CravingSpirit nó là một [copy constructor] (http://en.wikipedia.org/wiki/Copy_constructor) (với tên đối số bị bỏ qua). – Motti

+0

Cảm ơn. Tại sao chúng ta cần một nhà xây dựng bản sao nếu đã có một toán tử = quá tải? – qed

16

trả lời ngắn: Có bạn sẽ cần phải lặp lại công việc trong D

Long trả lời:

Nếu lớp có nguồn gốc của 'D' không chứa các biến thành viên mới thì phiên bản mặc định (được tạo ra bởi trình biên dịch nên làm việc tốt). Trình tạo bản sao mặc định sẽ gọi hàm tạo bản sao gốc và toán tử gán mặc định sẽ gọi toán tử gán cha.

Nhưng nếu lớp học 'D' của bạn chứa tài nguyên thì bạn sẽ cần thực hiện một số công việc.

tôi thấy nhà xây dựng bản sao của bạn một chút lạ:

B(const B& b){(*this) = b;} 

D(const D& d){(*this) = d;} 

thường copy constructor chuỗi để họ được copy xây dựng từ cơ sở lên. Ở đây bởi vì bạn đang gọi toán tử gán, hàm tạo bản sao phải gọi hàm khởi tạo mặc định để khởi tạo mặc định đối tượng từ dưới lên trước. Sau đó, bạn đi xuống một lần nữa bằng cách sử dụng toán tử gán. Điều này có vẻ không hiệu quả.

Bây giờ nếu bạn thực hiện một nhiệm vụ bạn đang sao chép từ dưới lên (hoặc trên xuống) nhưng có vẻ như khó khăn cho bạn để làm điều đó và cung cấp một bảo đảm ngoại lệ mạnh mẽ. Nếu tại bất kỳ điểm nào một tài nguyên không sao chép và bạn ném một ngoại lệ đối tượng sẽ ở trong trạng thái không xác định (đó là một điều xấu).

Thông thường tôi đã thấy nó được thực hiện theo cách khác.
Toán tử gán được xác định theo điều khoản của hàm tạo bản sao và trao đổi. Điều này là bởi vì nó làm cho nó dễ dàng hơn để cung cấp bảo đảm ngoại lệ mạnh mẽ. Tôi không nghĩ rằng bạn sẽ có thể cung cấp đảm bảo mạnh mẽ bằng cách làm theo cách này xung quanh (tôi có thể sai).

class X 
{ 
    // If your class has no resources then use the default version. 
    // Dynamically allocated memory is a resource. 
    // If any members have a constructor that throws then you will need to 
    // write your owen version of these to make it exception safe. 


    X(X const& copy) 
     // Do most of the work here in the initializer list 
    { /* Do some Work Here */} 

    X& operator=(X const& copy) 
    { 
     X tmp(copy);  // All resource all allocation happens here. 
          // If this fails the copy will throw an exception 
          // and 'this' object is unaffected by the exception. 
     swap(tmp); 
     return *this; 
    } 
    // swap is usually trivial to implement 
    // and you should easily be able to provide the no-throw guarantee. 
    void swap(X& s) throws() 
    { 
     /* Swap all members */ 
    } 
}; 

Thậm chí nếu bạn lấy được một lớp D từ X thì điều này không ảnh hưởng đến mẫu này.
Phải thừa nhận rằng bạn cần phải lặp lại một chút công việc bằng cách thực hiện các cuộc gọi rõ ràng vào lớp cơ sở, nhưng điều này là tương đối nhỏ.

class D: public X 
{ 

    // Note: 
    // If D contains no members and only a new version of foo() 
    // Then the default version of these will work fine. 

    D(D const& copy) 
     :X(copy) // Chain X's copy constructor 
     // Do most of D's work here in the initializer list 
    { /* More here */} 



    D& operator=(D const& copy) 
    { 
     D tmp(copy);  // All resource all allocation happens here. 
          // If this fails the copy will throw an exception 
          // and 'this' object is unaffected by the exception. 
     swap(tmp); 
     return *this; 
    } 
    // swap is usually trivial to implement 
    // and you should easily be able to provide the no-throw guarantee. 
    void swap(D& s) throws() 
    { 
     X::swap(s); // swap the base class members 
     /* Swap all D members */ 
    } 
}; 
+0

+1. Vì bạn đang ở đó, hãy thêm một chuyên môn vào std :: swap cho kiểu của bạn đại diện cho phương thức thành viên swap của bạn: 'namespace std {template <> void std :: swap (D & lhs, D & rhs) {lhs.swap (rhs); }} 'Bằng cách này, hoạt động hoán đổi chuyên dụng có thể được sử dụng trong các thuật toán STL. –

+1

Thêm một chức năng hoán đổi miễn phí trong cùng một không gian tên như X * nên * có cùng tác dụng (thông qua ADL), nhưng ai đó đã nói gần đây rằng MSVC gọi sai std :: swap một cách rõ ràng, do đó làm cho dribeas phải ... –

+0

Ngoài ra kỹ thuật bạn không được phép thêm nội dung vào không gian tên chuẩn. –

4

Bạn rất có thể có một lỗ hổng trong thiết kế của bạn (gợi ý: slicing, ngữ nghĩa thực thể vs ngữ nghĩa giá trị). Có một bản sao đầy đủ/ngữ nghĩa giá trị trên một đối tượng từ một hệ thống phân cấp đa hình thường không cần thiết chút nào. Nếu bạn muốn cung cấp nó chỉ trong trường hợp người ta có thể cần nó sau này, nó có nghĩa là bạn sẽ không bao giờ cần nó.Làm cho lớp cơ sở không thể sao chép được (ví dụ: bằng cách kế thừa từ boost :: noncopyable chẳng hạn), và đó là tất cả.

Các giải pháp duy nhất đúng khi đó nhu cầu thực sự xuất hiện là những thành ngữ phong bì chữ, hoặc khuôn khổ nhỏ từ bài báo trên Đối tượng thường xuyên bởi Sean mẹ và Alexander Stepanov IIRC. Tất cả các giải pháp khác sẽ cho bạn khó khăn khi cắt, và/hoặc LSP.

1

Bạn sẽ phải xác định lại tất cả các nhà thầu không phải là mặc định hoặc sao chép nhà thầu. Bạn không cần phải xác định lại constructor sao chép hay toán tử gán như những người được cung cấp bởi trình biên dịch (theo tiêu chuẩn) sẽ gọi tất cả các phiên bản của cơ sở:

struct base 
{ 
    base() { std::cout << "base()" << std::endl; } 
    base(base const &) { std::cout << "base(base const &)" << std::endl; } 
    base& operator=(base const &) { std::cout << "base::=" << std::endl; } 
}; 
struct derived : public base 
{ 
    // compiler will generate: 
    // derived() : base() {} 
    // derived(derived const & d) : base(d) {} 
    // derived& operator=(derived const & rhs) { 
    // base::operator=(rhs); 
    // return *this; 
    // } 
}; 
int main() 
{ 
    derived d1;  // will printout base() 
    derived d2 = d1; // will printout base(base const &) 
    d2 = d1;   // will printout base::= 
} 

Lưu ý rằng, như SBI lưu ý, nếu bạn xác định bất kỳ constructor trình biên dịch sẽ không tạo ra hàm tạo mặc định cho bạn và bao gồm hàm tạo bản sao.

+0

Lưu ý rằng trình biên dịch sẽ không cung cấp một ctor mặc định nếu bất kỳ ctor khác (điều này bao gồm ctor sao chép) được định nghĩa. Vì vậy, nếu bạn muốn 'bắt nguồn' để có một ctor mặc định, bạn sẽ cần phải xác định một cách rõ ràng. – sbi

1

Mã gốc là sai:

class B 
{ 
public: 
    B(const B& b){(*this) = b;} // copy constructor in function of the copy assignment 
    B& operator= (const B& b); // copy assignment 
private: 
// private member variables and functions 
}; 

Nói chung, bạn không thể xác định các nhà xây dựng bản sao về việc chuyển nhượng bản sao, bởi vì việc chuyển nhượng bản sao phải giải phóng tài nguyên và các nhà xây dựng bản sao don 't !!!

Để hiểu điều này, hãy xem xét:

class B 
{ 
public: 
    B(Other& ot) : ot_p(new Other(ot)) {} 
    B(const B& b) {ot_p = new Other(*b.ot_p);} 
    B& operator= (const B& b); 
private: 
    Other* ot_p; 
}; 

Để tránh rò rỉ bộ nhớ, việc chuyển nhượng bản sao đầu tiên phải xóa bộ nhớ được trỏ bởi ot_p:

B::B& operator= (const B& b) 
{ 
    delete(ot_p); // <-- This line is the difference between copy constructor and assignment. 
    ot_p = new Other(*b.ot_p); 
} 
void f(Other& ot, B& b) 
{ 
    B b1(ot); // Here b1 is constructed requesting memory with new 
    b1 = b; // The internal memory used in b1.op_t MUST be deleted first !!! 
} 

Vì vậy, copy constructor và sao chép phân công là khác nhau vì trước đây xây dựng và đối tượng vào bộ nhớ khởi tạo và, sau đó, PHẢI phát hành bộ nhớ hiện có trước khi xây dựng đối tượng mới.

Nếu bạn làm những gì được ban đầu gợi ý trong bài viết này:

B(const B& b){(*this) = b;} // copy constructor 

bạn sẽ được xóa một ký ức unexisting.

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