2012-10-31 33 views
7

Gần đây tôi đã gặp phải sự cố trong khi cố gắng triển khai phân cấp lớp với các trình xây dựng chuyển tiếp hoàn hảo. Hãy xem xét ví dụ sau:Xung đột giữa hàm tạo chuyển tiếp hoàn hảo và hàm tạo bản sao trong phân cấp lớp

struct TestBase { 
    template<typename T> 
    explicit TestBase(T&& t) : s(std::forward<T>(t)) {} // Compiler refers to this line in the error message 

    TestBase(const TestBase& other) : s(other.s) {} 

    std::string s; 
}; 

struct Test : public TestBase { 
    template<typename T> 
    explicit Test(T&& t) : TestBase(std::forward<T>(t)) {} 

    Test(const Test& other) : TestBase(other) {} 
}; 

Khi tôi cố gắng để biên dịch mã tôi nhận được lỗi sau:

Error 3 error C2664: 'std::basic_string<_Elem,_Traits,_Alloc>::basic_string(const std::basic_string<_Elem,_Traits,_Alloc> &)' : cannot convert parameter 1 from 'const Test' to 'const std::basic_string<_Elem,_Traits,_Alloc> &'

sự hiểu biết của tôi là trình biên dịch xử lý các nhà xây dựng chuyển tiếp hoàn hảo như một toán tốt hơn so với copy constructor. Xem ví dụ Scott Meyers: Copying Constructors in C++11. Trong các triển khai khác mà không có phân cấp lớp, tôi có thể vô hiệu hóa trình xây dựng chuyển tiếp hoàn hảo từ một trình tạo bản sao thông qua SFINAE. Xem ví dụ Martinho Fernandes: Some pitfalls with forwarding constructors. Khi tôi cố gắng áp dụng giải pháp đã đề cập cho ví dụ này, tôi vẫn không thể biên dịch với cùng một thông báo lỗi.

Tôi nghĩ một giải pháp có thể là tránh chuyển tiếp hoàn hảo, lấy các tham số theo giá trị trong các hàm tạo và di chuyển từ chúng sang các biến lớp.

Vì vậy, câu hỏi của tôi là nếu có một số giải pháp khác cho vấn đề này hoặc nếu chuyển tiếp hoàn hảo không thể trong trường hợp này?

Cập nhật: Hóa ra câu hỏi của tôi dễ hiểu. Vì vậy, tôi sẽ cố gắng làm sáng tỏ ý định của mình và bối cảnh một chút.

  • Mã hoàn tất như được đăng trong câu hỏi. Không có đối tượng nào khác được tạo hoặc hàm được gọi. Lỗi xuất hiện trong khi cố gắng biên dịch ví dụ đã đăng.
  • Mục đích của việc tạo hàm tạo chuyển tiếp hoàn hảo là khởi tạo thành viên và không phải để có một số loại hàm tạo bản sao phụ. Lý do ở đây là để lưu một số bản sao đối tượng khi khởi tạo các thành viên với các đối tượng tạm thời (như được đề xuất trong các cuộc đàm phán của Scott Meyers)
  • Thật không may là nó đã trở thành nhà xây dựng chuyển tiếp hoàn hảo có thể xung đột với các nhà xây dựng quá tải khác (trong ví dụ này với các nhà xây dựng sao chép) .
  • Giống như các câu trả lời và nhận xét cho câu hỏi này được đề xuất: Các giải pháp có thể có ở đây là giới thiệu các công thức rõ ràng hoặc có các nhà xây dựng không có quy trình riêng (ví dụ: về ví dụ có hai hàm tạo với tham số const string&string&& tương ứng).
+0

nó Có thể mà bạn đã viết một cái gì đó như 'std :: string (thử nghiệm)' thay vì 'std :: string (test.s) '? Vui lòng chỉ cho chúng tôi dòng 130 của main.cc – Andrey

+2

Bạn đã viết 's (std :: forward (t))' thay vì 's (std :: forward (t) .s)'. – avakar

+1

Nói chung là một ý tưởng tồi để trộn lẫn quá tải và mẫu chuyển tiếp. Tôi muốn tạo một constructor đơn từ 'string' và thêm một mẫu' make_test' * function * chuyển tiếp. –

Trả lời

2

Hãy thử thay đổi Test(const Test& other) : TestBase(other) {}-Test(const Test& other) : TestBase(static_cast<TestBase const&>(other)) {}

The 2nd Test constructor được gọi TestBase, và có hai khả năng. Một trong số họ mất bất cứ điều gì, người kia có một TestBase. Nhưng bạn đang vượt qua một bài kiểm tra cho nó - "bất cứ điều gì" phù hợp hơn. Bằng cách đúc một cách rõ ràng đến một TestBase const &, chúng ta sẽ có thể có được một kết hợp phù hợp.

Một khả năng khác có thể liên quan đến cách thử nghiệm được xây dựng - có lẽ những gì bạn đã vượt qua trong khớp với hàm tạo mẫu để kiểm tra thay thế? Chúng ta có thể kiểm tra khả năng khác này bằng cách loại bỏ hàm tạo mẫu từ Kiểm tra và xem nếu lỗi đó biến mất.

Nếu trường hợp đó xảy ra, tại sao kỹ thuật bạn không liên kết (để vô hiệu hóa hàm tạo mẫu Thử nghiệm khi loại suy luận khớp với Kiểm tra) hoạt động?

+0

Giải pháp của bạn biên dịch mà không có lỗi. Có thể có bất kỳ tác dụng phụ có thể có trong khi áp dụng giải pháp của bạn? Thông thường tôi muốn được một chút cẩn thận khi im lặng lỗi/cảnh báo với phôi. – mkh

+0

Dàn diễn viên được đề cập là an toàn. Tôi cast một Foo const & vào một const FooBase & - một cái gì đó mà bạn muốn làm ngầm, nhưng trình biên dịch ưa thích của bạn constructor mẫu mà chấp nhận tất cả mọi thứ. Tôi đã làm rõ những gì bạn muốn hoàn toàn xảy ra ... – Yakk

+0

Cảm ơn lời giải thích. Một câu hỏi nữa: Nếu bây giờ tôi thêm một hàm khởi tạo để Test như sau: 'Test (Test && other): TestBase (std :: move (other)) {}'. Tôi lại nhận được thông báo lỗi tương tự. Bạn có thể cũng có một giải pháp cho tình huống này? Xe tăng rất nhiều! – mkh

1

Hãy xem xét kỹ hơn thông báo lỗi.

std::basic_string<...>::basic_string(const std::basic_string<...> &) :

Điều đó có nghĩa nó áp dụng cho một nhà xây dựng bản sao của std::string

cannot convert parameter 1 from 'const Test' to 'const std::basic_string<..> &

Thật vậy, không có cách nào để chuyển đổi Test-std::string. Tuy nhiên, Test có một thành viên chuỗi, cụ thể là, std::string s;.

Kết luận: có vẻ như bạn quên thêm .s tại địa điểm đó. Có lẽ, nó nằm trong s(std::forward<T>(t)).

Một lý do khác có thể là quá tải 1 của nhà xây dựng đã được chọn thay vì thứ 2 để tạo bản sao cho phiên bản Test.

+0

Tôi không nghĩ mình đã quên '.s'. Ý tưởng của tôi là tham số t chính nó là một loại chuỗi mà tôi muốn chuyển tiếp đến thành viên. Ví dụ: const char * hoặc std :: string. – mkh

+0

@mkh: Vậy nó có nghĩa là nó xảy ra bằng cách nào đó T không phải là một chuỗi mà là 'Test'. – Andrey

+0

Hơn câu hỏi của tôi sẽ là làm thế nào tôi có thể làm cho trình biên dịch ngừng suy nghĩ rằng T có thể có thể là một thử nghiệm. Tôi không tạo bất kỳ đối tượng nào hoặc gọi bất kỳ chức năng nào. – mkh

1

Sau đây nên làm việc và nó sử dụng không có dàn diễn viên rõ ràng:

struct Test : public TestBase { 
    private: 
    static TestBase const& toBase(const Test& o) { return o; } 

    public: 
    template <typename T> 
    explicit Test(T&& t) : TestBase(std::forward<T>(t)) {} 

    Test(const Test& other) : TestBase(toBase(other)) {} 
}; 
Các vấn đề liên quan