2014-04-30 13 views
20

Tôi đang sử dụng thư viện có lớp học với hàm init khác với hàm tạo của nó. Mỗi lần tôi làm cho một trường hợp mới tôi cần phải gọi, ví dụ:Tôi có thể tạo biến _const từ bây giờ trở đi_ không?

MyClass a; 
a.init(); 

Kể từ init không phải là const, điều này ngăn cản tôi từ việc tạo trường const (Tôi không thể viết const MyClass a). Có cách nào để gọi init và sau đó tuyên bố từ "ở đây trên" (tôi đoán cho phần còn lại của phạm vi) biến của tôi là const?

này hoạt động, nhưng dựa vào không chạm vào biến ban đầu:

MyClass dont_touch; 
dont_touch.init(); 
const MyClass & a = dont_touch; 
+0

Câu trả lời là không. Giống như bạn, tôi muốn nó là có. – Mehrdad

+0

Bạn cũng có thể sử dụng 'const_cast' cho lời gọi' init' để thực hiện nhanh và dơ bẩn. Tức là, khai báo đối tượng 'const' và' const_cast' xung quanh đối tượng cho lệnh 'init'. Tuy nhiên, 'const_cast' thường được coi là thực hành không tốt, và tôi phải nói rằng tôi nghĩ rằng [giải pháp templated] (http://stackoverflow.com/a/23400028/3100771) là siêu trơn. – Apriori

+1

@Apriori Chắc chắn hành vi không xác định. Không thể const_cast đi const từ một đối tượng const được xác định và làm công cụ không const cho nó. –

Trả lời

16

Nếu bạn đang sử dụng C++ 11 bạn có thể sử dụng một hàm lambda

const MyClass ConstantVal = []{ 
    MyClass a; 
    a.init(); 
    return a; 
}(); 

này cho phép bạn để giữ cho khởi tạo tại chỗ trong khi không bao giờ cho phép truy cập bên ngoài để các đối tượng có thể thay đổi. also see: http://herbsutter.com/2013/04/05/complex-initialization-for-a-const-variable/

+1

Tôi nghĩ rằng điều này là tối ưu hơn so với giải pháp của tôi cũng có, kể từ khi trình biên dịch sau đó có thể giả định rằng đối tượng không bao giờ sửa đổi. –

+0

Điều này trả lời câu hỏi cụ thể của tôi. Mặc dù tôi có thể hỏi một câu hỏi tổng quát hơn khi 'a.init()' được thay thế bằng một số thao tác tùy ý có thể không hợp lý trong lambda. –

5

Tạo một chức năng mà kết thúc tốt đẹp hai dòng đầu tiên và mang đến cho bạn một đối tượng mà đã sẵn sàng để đi.

MyClass makeMyClass() 
{ 
    MyClass a; 
    a.init(); 
    return a; 
} 

// Now you can construct a const object or non-const object. 
const MyClass a = makeMyClass(); 
MyClass b = makeMyClass(); 

Cập nhật

Sử dụng makeMyClass() liên quan đến việc xây dựng và phá hủy một đối tượng tạm thời mọi hàm được gọi. Nếu điều đó trở thành chi phí đáng kể, makeMyClass() có thể được thay đổi thành:

MyClass const& makeMyClass() 
{ 
    static bool inited = false; 
    static MyClass a; 
    if (!inited) 
    { 
    inited = true; 
    a.init(); 
    } 
    return a; 
} 

Cách sử dụng, như được mô tả trước đó sẽ tiếp tục hoạt động. Ngoài ra, một khi cũng có thể thực hiện việc này:

const MyClass& c = makeMyClass(); 
+0

Điều này xảy ra với chi phí của một bản sao, đúng không? –

+1

Có, nó có giá bằng một bản sao. –

+3

@mangledorf Tôi không chắc chắn về điều này, nhưng [tối ưu hóa giá trị trả lại] (https://en.wikipedia.org/wiki/Return_value_optimization) có thể hữu ích. Vì vậy, nếu tôi hiểu nó đúng, nếu bạn sử dụng '-O3', tôi không nghĩ bạn cần phải lo lắng về điều này. Chỉ là một ý nghĩ. – gongzhitaao

9

Bạn có thể tạo lớp trình bao bọc và sử dụng thay thế.

Nếu MyClass có một destructor ảo mà bạn có thể cảm thấy bắt nguồn an toàn từ nó như thế này:

class WrapperClass : public MyClass 
{ 
public: 
    WrapperClass() 
    { 
     init(); // Let's hope this function doesn't throw 
    } 
}; 

Hoặc viết một lớp có chứa các ví dụ MyClass

class WrapperClass 
{ 
public: 
    WrapperClass() 
    { 
     m_myClass.init(); // Let's hope this function doesn't throw 
    } 
    operator MyClass&() {return m_myClass;} 
    operator const MyClass&() const {return m_myClass;} 
private: 
    MyClass m_myClass; 
}; 

Hoặc viết một bản mẫu để giải quyết này vấn đề chung khi sử dụng một trong hai giải pháp trên: ví dụ:

template <class T> class WrapperClass : public T 
{ 
public: 
    WrapperClass() 
    { 
     T::init(); 
    } 
}; 

typedef WrapperClass<MyClass> WrapperClass; 
+0

Có thể đáng chú ý là một lớp dẫn xuất có thể được định nghĩa bên trong và cục bộ cho hàm mà nó được sử dụng nếu muốn. Nhưng việc sử dụng giải pháp mẫu thậm chí còn tốt hơn và tránh được điều này, bởi vì bạn chỉ có thể khởi tạo theo ý muốn. – Apriori

+1

Nó hoàn toàn tốt cho 'init' để ném. Hãy hy vọng nó không trả về một mã lỗi. Lý do thông thường cho các hàm 'init' là nhà phát triển biết một lỗi có thể xảy ra, nhưng không biết ngoại lệ, vì vậy anh ta không thể báo cáo lỗi nếu nó xảy ra trong ctor. – MSalters

1

Bạn thực sự có thể làm điều đó khá đơn giản, thậm chí không có C++ 11 và lambdas:

const MyClass a; 
{ 
    MyClass _a; 
    _a.init(); 
    std::swap(const_cast<MyClass&>(a), _a); 
} 

Việc sử dụng const_cast là phải thừa nhận là một chút của một hack, nhưng nó đã giành' t phá vỡ bất cứ điều gì như const là một specifier khá yếu. Đồng thời, nó khá hiệu quả, vì đối tượng MyClass chỉ đổi chỗ, không được sao chép (hầu hết các đối tượng đắt tiền để sao chép hợp lý nên cung cấp chức năng swap và bơm quá tải std::swap).

Nếu không có các diễn viên, nó sẽ đòi hỏi một helper:

struct Construct_Init { 
    operator MyClass() const 
    { 
     MyClass a; 
     a.init(); 
     return a; 
    } 
}; 
const MyClass a = Construct_Init(); 

Đây có thể là như thế này trong một hàm (cấu trúc Construct_Init cần không được khai báo ở phạm vi không gian tên), nhưng nó là lâu hơn một chút. Bản sao của đối tượng có thể hoặc không được tối ưu hóa bằng cách sử dụng bản sao.

Lưu ý rằng trong cả hai trường hợp, giá trị trả lại của init() bị mất. Nếu nó trả về một boolean nơi true là thành công và false là thất bại, nó là tốt hơn để:

if(!a.init()) 
    throw std::runtime_error("MyClass init failed"); 

Hoặc chỉ cần đảm bảo để xử lý các lỗi một cách thích hợp.

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