2015-05-05 16 views
5

Với struct sau:bản sao khởi vs danh sách khởi tạo trực tiếp tạm

struct ABC 
{ 
    ABC(){cout << "ABC" << endl;} 
    ~ABC() noexcept {cout << "~ABC" << endl;} 
    ABC(ABC const&) {cout << "copy" << endl;} 
    ABC(ABC&&) noexcept {cout << "move" << endl;} 
    ABC& operator=(ABC const&){cout << "copy=" << endl;} 
    ABC& operator=(ABC&&) noexcept {cout << "move=" << endl;} 
}; 

Sản lượng:

std::pair<std::string, ABC> myPair{{}, {}}; 

là:

ABC 
copy 
~ABC 
~ABC 

Trong khi đầu ra của:

std::pair<std::string, ABC> myPair{{}, ABC{}}; 

là:

ABC 
move 
~ABC 
~ABC 

Trong cố gắng để hiểu sự khác biệt giữa hai Tôi nghĩ rằng tôi đã xác định rằng trường hợp đầu tiên được sử dụng bản sao-list-khởi tạo, trong khi một thứ hai sử dụng trực tiếp-list-khởi của một chưa được đặt tên tạm thời (số 7 và 2, tương ứng, tại đây: http://en.cppreference.com/w/cpp/language/list_initialization).

Tìm kiếm các câu hỏi tương tự Tôi đã tìm thấy điều này: Why does the standard differentiate between direct-list-initialization and copy-list-initialization? và điều này: Does copy list initialization invoke copy ctor conceptually?.

Câu trả lời trong những câu hỏi này thảo luận về thực tế là để khởi tạo danh sách sao chép, việc sử dụng một hàm tạo rõ ràng sẽ làm cho mã không đúng định dạng. Trong thực tế, nếu tôi làm cho constructor mặc định của ABC rõ ràng, ví dụ đầu tiên của tôi sẽ không biên dịch nhưng đó là (có lẽ) một vấn đề khác.

Vì vậy, câu hỏi đặt ra là: Tại sao bản sao được sao chép tạm thời trong trường hợp đầu tiên nhưng được di chuyển trong trường hợp thứ hai? Điều gì ngăn cản nó được di chuyển trong trường hợp khởi tạo sao chép danh sách?

Là một lưu ý, các mã sau đây:

std::pair<std::string, ABC> myPair = std::make_pair<string, ABC>({}, {}); 

Ngoài ra kết quả trong một cuộc gọi đến constructor di chuyển của ABC (và không có cuộc gọi constructor sao chép), nhưng cơ chế khác nhau có thể tham gia.

Bạn có thể thử mã ra (sử dụng gcc-4.9.2 trong chế độ 14 C++) tại địa chỉ: https://ideone.com/Kc8xIn

Trả lời

8

Nói chung, chuẩn bị tinh thần-init-danh sách như {} không biểu hiện và không có một loại. Nếu bạn có một hàm template

template<typename T> void f(T); 

và gọi f({}), không có loại sẽ được rút ra cho T, và loại trừ sẽ thất bại.

Mặt khác, ABC{} là biểu thức giá trị của loại ABC (một "chuyển đổi loại rõ ràng trong ký pháp chức năng"). Đối với một cuộc gọi như f(ABC{}), mẫu chức năng có thể suy ra loại ABC từ biểu thức này.


Trong C++ 14, cũng như trong C++ 11, std::pair có các hàm tạo sau đây [cặp.đôi]; T1T2 là tên của các mẫu tham số của mẫu std::pair lớp:

pair(const pair&) = default; 
pair(pair&&) = default; 
constexpr pair(); 
constexpr pair(const T1& x, const T2& y); 
template<class U, class V> constexpr pair(U&& x, V&& y); 
template<class U, class V> constexpr pair(const pair<U, V>& p); 
template<class U, class V> constexpr pair(pair<U, V>&& p); 
template <class... Args1, class... Args2> 
pair(piecewise_construct_t, tuple<Args1...>, tuple<Args2...>); 

Lưu ý rằng có một constructor

constexpr pair(const T1& x, const T2& y); // (C) 

Nhưng không

constexpr pair(T1&& x, T2&& y); 

thay vào đó, có một cách hoàn hảo chuyển tiếp

template<class U, class V> constexpr pair(U&& x, V&& y); // (P) 

Nếu bạn cố gắng khởi tạo một std::pair với hai initializers nơi có ít nhất một trong số họ là một chuẩn bị tinh thần-init-list , hàm tạo (P) là không khả thi vì nó không thể suy ra các đối số mẫu của nó.

(C) không phải là mẫu hàm tạo. Các kiểu tham số T1 const&T2 const& của nó được cố định bởi tham số mẫu lớp. Tham chiếu đến loại liên tục có thể được khởi tạo từ một số trống braced-init-list. Điều này tạo ra một đối tượng tạm thời được ràng buộc với tham chiếu. Khi kiểu được gọi là const, hàm tạo (C) sẽ sao chép các đối số của nó vào các thành viên dữ liệu của lớp.


Khi bạn khởi tạo một cặp qua std::pair<T,U>{ T{}, U{} }, các T{}U{} là prvalue-biểu thức. Mẫu hàm tạo (P) có thể suy ra các kiểu của chúng và khả thi. Sự khởi tạo được tạo ra sau khi loại trừ là một kết hợp tốt hơn so với hàm tạo (C), bởi vì (P) sẽ tạo ra các tham số tham chiếu rvalue và ràng buộc các đối số prvalue cho chúng. (C) mặt khác liên kết các đối số prvalue với tham chiếu lvalue.


Tại sao ví dụ trực tiếp chuyển đối số thứ hai khi được gọi qua std::pair<T,U>{ {}, U{} }?

libstdC++ định nghĩa các hàm tạo bổ sung. Dưới đây là phần trích xuất của việc triển khai std::pair từ 78536ab78e, bỏ qua các định nghĩa chức năng, một số nhận xét và SFINAE. _T1_T2 là tên của các thông số mẫu của mẫu lớp học std::pair.

_GLIBCXX_CONSTEXPR pair(); 

    _GLIBCXX_CONSTEXPR pair(const _T1& __a, const _T2& __b); // (C) 

    template<class _U1, class _U2> 
constexpr pair(const pair<_U1, _U2>& __p); 

    constexpr pair(const pair&) = default; 
    constexpr pair(pair&&) = default; 

    // DR 811. 
    template<class _U1> 
constexpr pair(_U1&& __x, const _T2& __y); // (X) 

    template<class _U2> 
constexpr pair(const _T1& __x, _U2&& __y); // (E) <===================== 

    template<class _U1, class _U2> 
constexpr pair(_U1&& __x, _U2&& __y);  // (P) 

    template<class _U1, class _U2> 
constexpr pair(pair<_U1, _U2>&& __p); 

    template<typename... _Args1, typename... _Args2> 
    pair(piecewise_construct_t, tuple<_Args1...>, tuple<_Args2...>); 

Lưu ý mẫu (E) constructor: Nó sẽ sao chép đối số đầu tiên và hoàn toàn chuyển tiếp thứ hai. Đối với một khởi tạo như std::pair<T,U>{ {}, U{} }, nó là khả thi vì nó chỉ cần suy ra một loại từ đối số thứ hai. Nó cũng là một trận đấu tốt hơn (C) cho đối số thứ hai, và do đó một trận đấu tốt hơn tổng thể.

Nhận xét "DR 811" nằm trong nguồn libstdC++. Nó đề cập đến LWG DR 811 thêm một số SFINAE, nhưng không có hàm tạo mới.

Các hàm tạo (E) và (X) là phần mở rộng libstdC++. Tuy nhiên, tôi không chắc liệu nó có tuân thủ hay không.

libC++ mặt khác không có các hàm tạo bổ sung này. Đối với ví dụ std::pair<T,U>{ {}, U{} }, nó sẽ copy the second argument.

Live demo with both library implementations

+1

thế nào có thể hoàn toàn chuyển tiếp constructor được lựa chọn cho 'myPair {{}, ABC {}};' cuộc gọi, nếu nó không thể suy ra 'std :: chuỗi' từ '{}'? –

+0

@MarcAndreson Rất tiếc, tệ của tôi. Tôi đã hiểu sai điều này. Nếu bạn đang sử dụng libstdC++, đây có thể là một phần mở rộng. Hãy để tôi cố gắng tìm nó. – dyp

+1

Lý do cho những quá tải đó chủ yếu là để hỗ trợ các công cụ như 'std :: pair , int *> p (std :: unique_ptr (), 0);'. Xem [PR 40925] (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=40925#c8), –

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