2012-05-21 25 views
24

Tôi đang cập nhật cấu trúc của tôi và tôi muốn thêm một thành viên chuỗi :: std vào nó. Các cấu trúc ban đầu trông như thế này:C++ 11 liên kết ẩn danh với các thành viên không tầm thường

struct Value { 
    uint64_t lastUpdated; 

    union { 
    uint64_t ui; 
    int64_t i; 
    float f; 
    bool b; 
    }; 
}; 

Chỉ cần thêm một thành viên std :: string để công đoàn, tất nhiên, gây ra một lỗi biên dịch, bởi vì một thường sẽ cần phải thêm các nhà thầu không tầm thường của đối tượng. In the case of std::string (text from informit.com)

Since std::string defines all of the six special member functions, U will have an implicitly deleted default constructor, copy constructor, copy assignment operator, move constructor, move assignment operator and destructor. Effectively, this means that you can't create instances of U unless you define some, or all of the special member functions explicitly.

Sau đó trang web tiếp tục cung cấp cho các mẫu mã sau:

union U 
{ 
int a; 
int b; 
string s; 
U(); 
~U(); 
}; 

Tuy nhiên, tôi đang sử dụng một công đoàn vô danh trong một cấu trúc. Tôi hỏi ## C++ trên freenode và họ nói với tôi một cách chính xác để làm điều đó là để đưa các nhà xây dựng trong struct thay vào đó và đã cho tôi mã ví dụ này:

#include <new> 

struct Point { 
    Point() {} 
    Point(int x, int y): x_(x), y_(y) {} 
    int x_, y_; 
}; 

struct Foo 
{ 
    Foo() { new(&p) Point(); } 
    union { 
    int z; 
    double w; 
    Point p; 
    }; 
}; 

int main(void) 
{ 
} 

Nhưng từ đó tôi không thể hình dung làm thế nào để làm cho phần còn lại của các chức năng đặc biệt mà std :: string cần xác định, và hơn nữa, tôi không hoàn toàn rõ ràng về cách ctor trong ví dụ đó đang làm việc.

Tôi có thể nhờ ai đó giải thích điều này cho tôi một chút rõ ràng hơn không?

+2

Dường như với tôi như những gì bạn cần là một [biến thể] thích hợp (http://www.boost.org/libs/variant/) ... – ildjarn

+1

Tôi đã có một lớp biến thể mà tôi đang sử dụng ở nơi khác . Tôi không sử dụng nó trong trường hợp này, vì đây là dữ liệu tuần tự trên mạng và tôi muốn giữ dữ liệu nhỏ, vì vậy mọi thứ mà Biến thể giữ nội bộ (như nhập thông tin) vào lớp tôi muốn giữ bên ngoài và quyết định những gì dựa trên lược đồ gói. – OmnipotentEntity

Trả lời

21

Không cần phải có vị trí mới tại đây.

thành viên Ngôn ngữ địa phương sẽ không được khởi tạo bởi các nhà xây dựng trình biên dịch tạo ra, nhưng không nên có rắc rối chọn một và khởi tạo nó bằng cách sử dụng bình thường ctor-initializer-list. Các thành viên được khai báo bên trong các tổ chức vô danh thực sự là các thành viên của lớp chứa, và có thể được khởi tạo trong hàm tạo của lớp chứa.

Hành vi này được mô tả trong phần 9.5.[class.union]:

A union-like class is a union or a class that has an anonymous union as a direct member. A union-like class X has a set of variant members. If X is a union its variant members are the non-static data members; otherwise, its variant members are the non-static data members of all anonymous unions that are members of X .

và trong phần 12.6.2 [class.base.init]:

A ctor-initializer may initialize a variant member of the constructor’s class. If a ctor-initializer specifies more than one mem-initializer for the same member or for the same base class, the ctor-initializer is ill-formed.

Vì vậy, các mã có thể chỉ đơn giản là:

#include <new> 

struct Point { 
    Point() {} 
    Point(int x, int y): x_(x), y_(y) {} 
    int x_, y_; 
}; 

struct Foo 
{ 
    Foo() : p() {} // usual everyday initialization in the ctor-initializer 
    union { 
    int z; 
    double w; 
    Point p; 
    }; 
}; 

int main(void) 
{ 
} 

Tất nhiên, vị trí mới vẫn nên được sử dụng khi sống động một biến thể thành viên khác với thành phần khác được khởi tạo trong hàm tạo.

+0

Điều gì sẽ xảy ra nếu hàm tạo 'Foo' được định nghĩa nhưng không chọn một trong số các biến thể thành viên đó (nghĩa là' Foo() {} 'thay vì' Foo(): p() {} ')? GCC 5.1 và Clang 3.6 bật ra để biên dịch constructor mà không có bất kỳ cảnh báo hoặc lỗi nào: http://melpon.org/wandbox/permlink/gLkD49UOrrGhFUJc Tuy nhiên, không rõ tiêu chuẩn nói gì về trường hợp đó. – dkim

+0

@dkim: Tôi khá chắc chắn rằng để lại tất cả các thành viên biến thể trong cùng một tiểu bang, lưu trữ cụ thể thu được nhưng khởi tạo không được thực hiện. Mục 3.8 (Object Lifetime) chỉ định các hoạt động được phép trên các thành viên trong trạng thái như vậy. –

13

Ví dụ new (&p) Point() ví dụ này là cuộc gọi đến vị trí tiêu chuẩn new nhà điều hành (thông qua biểu thức vị trí mới), do đó bạn cần bao gồm <new>. Toán tử cụ thể đó đặc biệt ở chỗ nó không phải là cấp phát bộ nhớ, nó chỉ trả về những gì bạn truyền cho nó (trong trường hợp này là tham số &p). Kết quả ròng của biểu thức là một đối tượng đã được xây dựng.

Nếu bạn kết hợp cú pháp này với các cuộc gọi destructor rõ ràng thì bạn có thể đạt được hoàn toàn kiểm soát thời gian tồn tại của một đối tượng:

// Let's assume storage_type is a type 
// that is appropriate for our purposes 
storage_type storage; 

std::string* p = new (&storage) std::string; 
// p now points to an std::string that resides in our storage 
// it was default constructed 

// *p can now be used like any other string 
*p = "foo"; 

// Needed to get around a quirk of the language 
using string_type = std::string; 

// We now explicitly destroy it: 
p->~string_type(); 
// Not possible: 
// p->~std::string(); 

// This did nothing to our storage however 
// We can even reuse it 
p = new (&storage) std::string("foo"); 

// Let's not forget to destroy our newest object 
p->~string_type(); 

Khi nào và nơi bạn nên xây dựng và phá hủy các std::string thành viên (chúng ta hãy gọi nó s) trong lớp Value tùy thuộc vào mẫu sử dụng của bạn cho s. Trong ví dụ tối thiểu này, bạn không bao giờ xây dựng (và do đó hủy) nó trong các thành viên đặc biệt: do đó

struct Value { 
    Value() {} 

    Value(Value const&) = delete; 
    Value& operator=(Value const&) = delete; 

    Value(Value&&) = delete; 
    Value& operator=(Value&&) = delete; 

    ~Value() {} 

    uint64_t lastUpdated; 

    union { 
     uint64_t ui; 
     int64_t i; 
     float f; 
     bool b; 
     std::string s; 
    }; 
}; 

Sau đây là một sử dụng hợp lệ Value:

Value v; 
new (&v.s) std::string("foo"); 
something_taking_a_string(v.s); 
using string_type = std::string; 
v.s.~string_type(); 

Như bạn có thể nhận thấy, tôi vô hiệu hóa sao chép và di chuyển Value. Lý do là chúng ta không thể sao chép hoặc di chuyển thành viên hoạt động thích hợp của liên minh mà không biết nó đang hoạt động, nếu có.

+5

* "// Cần thiết để tránh xung quanh ngôn ngữ" * - thực ra, điều này là sai - bạn * có thể gọi trực tiếp destructor, nếu bạn làm đúng (cần phải giải quyết chính xác phạm vi): 'p-> std :: string :: ~ string(); '. Dễ đọc hơn? Vâng, chắc chắn trông phức tạp hơn, nhưng sử dụng một kiểu dữ liệu nổi tiếng, trong khi giải pháp trên là gọn hơn (ngoài dòng mã bổ sung cho 'using'), nhưng giới thiệu một bí danh ít được biết đến. Chắc chắn là một vấn đề về sở thích cá nhân (liên quan đến bản thân tôi, tôi sẽ bỏ phiếu cho loại dữ liệu nổi tiếng ...). – Aconcagua