2014-12-18 14 views
6

Tôi đã gặp một số mã khiến tôi kinh hãi. Về cơ bản nó sau mô hình này:C++ Đang xây dựng đối tượng hai lần bằng cách sử dụng vị trí hành vi chưa xác định mới?

class Foo 
{ 
    public: 
    //default constructor 
    Foo(): x(0), ptr(nullptr) 
    { 
     //do nothing 
    } 

    //more interesting constructor 
    Foo(FooInitialiser& init): x(0), ptr(nullptr) 
    { 
     x = init.getX(); 
     ptr = new int; 
    } 
    ~Foo() 
    { 
     delete ptr; 
    } 

    private: 
    int x; 
    int* ptr; 
}; 


void someFunction(FooInitialiser initialiser) 
{ 
    int numFoos = MAGIC_NUMBER; 
    Foo* fooArray = new Foo[numFoos]; //allocate an array of default constructed Foo's 

    for(int i = 0; i < numFoos; ++i) 
    { 
     new(fooArray+ i) Foo(initialiser); //use placement new to initialise 
    } 

    //... do stuff 

    delete[] fooArray; 
} 

Mã này đã được trong các cơ sở mã trong nhiều năm và nó sẽ có vẻ chưa bao giờ gây ra một vấn đề. Nó rõ ràng là một ý tưởng tồi vì ai đó có thể thay đổi constructor mặc định để phân bổ không mong đợi việc xây dựng thứ hai. Đơn giản chỉ cần thay thế constructor thứ hai bằng một phương thức khởi tạo tương đương có vẻ là điều hợp lý để làm. ví dụ.

void Foo::initialise(FooInitialiser& init) 
{ 
    x = init.getX(); 
    ptr = new int; 
} 

Mặc dù vẫn có thể bị rò rỉ tài nguyên, ít nhất một lập trình viên phòng thủ có thể nghĩ để kiểm tra phân bổ trước theo phương pháp thông thường.

Câu hỏi của tôi là:

Được xây dựng hai lần như hành vi thực sự không xác định này/cấm theo tiêu chuẩn hoặc đơn giản chỉ là một ý tưởng tồi? Nếu hành vi không xác định bạn có thể trích dẫn hoặc chỉ cho tôi đúng nơi để xem trong tiêu chuẩn không?

+0

bạn đã thử valgrind trên mã này chưa? – zoska

+0

Vấn đề chính tôi thấy là 'Foo' không tuân thủ quy tắc của ba - bản sao mặc định-ctor và copy-assignment-operator sẽ không làm điều đúng với' Foo :: ptr'. – cdhowie

+0

@cdhowie Có lẽ chúng ta không nên giả định điều tồi tệ nhất về mã của người khác. Tôi đoán OP đơn giản là cắt mã không cần thiết để đặt câu hỏi. – anatolyg

Trả lời

10

Nói chung, làm việc với vị trí mới theo cách này không phải là một ý tưởng hay. Gọi một bộ khởi tạo từ cái mới đầu tiên, hoặc gọi bộ khởi tạo thay vì vị trí mới đều được coi là biểu mẫu tốt hơn mã bạn đã cung cấp.

Tuy nhiên, trong trường hợp này, hành vi gọi vị trí mới trên đối tượng hiện có được xác định rõ.

Một chương trình có thể kết thúc cuộc đời của bất kỳ đối tượng bằng cách tái sử dụng lưu trữ mà đối tượng chiếm hoặc bằng cách gọi một cách rõ ràng destructor cho một đối tượng của một loại lớp học với một destructor không tầm thường. Đối với một đối tượng của một loại lớp với một destructor không tầm thường, chương trình không phải là cần thiết để gọi destructor một cách rõ ràng trước khi lưu trữ mà đối tượng chiếm được tái sử dụng hoặc phát hành; tuy nhiên, nếu không có cuộc gọi rõ ràng tới trình hủy hoặc nếu biểu thức xóa (5.3.5) là không được sử dụng để giải phóng bộ nhớ, thì hàm hủy không được gọi là được gọi ngầm và bất kỳ chương trình nào phụ thuộc vào các tác dụng phụ được tạo ra bởi destructor có hành vi không xác định.

Vì vậy, khi điều này xảy ra:

Foo* fooArray = new Foo[numFoos]; //allocate an array of default constructed Foo's 

for(int i = 0; i < numFoos; ++i) 
{ 
    new(fooArray+ i) Foo(initialiser); //use placement new to initialise 
} 

Các vị trí hoạt động mới sẽ chấm dứt cuộc đời của Foo rằng có, và tạo một hình mới trong đó diễn ra. Trong nhiều trường hợp, điều này có thể là xấu, nhưng với cách thức hoạt động của destructor, điều này sẽ ổn thôi.

Gọi vị trí mới trên đối tượng hiện có có thể là hành vi không xác định, nhưng nó phụ thuộc vào đối tượng cụ thể.

Điều này không tạo ra hành vi không xác định, vì bạn không phụ thuộc vào "tác dụng phụ" do trình tạo hủy.

Các chỉ "tác dụng phụ" trong destructor của đối tượng của bạn là để delete các chứa int con trỏ, nhưng trong trường hợp này mà đối tượng là không bao giờ ở trong tình trạng deletable khi vị trí new được gọi.

Nếu có thể có con trỏ int chứa nullptr và có thể yêu cầu xóa, sau đó gọi vị trí new đối với đối tượng hiện có sẽ gọi hành vi không xác định.

+3

Trong khi chính xác, tôi muốn xem xét việc tái sử dụng này như là một ý tưởng khủng khiếp. –

+1

Có rất nhiều cấu trúc tương tự trong> codebase 30 tuổi của chúng tôi và tất cả đều là kết quả của quá trình chuyển đổi chưa hoàn thành từ c sang C++ –

+1

@MooingDuck Đồng ý. Cảm ơn, tôi đã nói rõ rằng đây có thể là một ý tưởng tồi nhưng trong trường hợp này nó được xác định rõ ràng. –

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