2014-09-27 12 views
13

Tôi đang lưu trữ một hành động cần vận hành trên một đối tượng nhưng tôi không muốn sử dụng thừa kế. Vì vậy, các kỹ thuật tôi đang sử dụng là phải có một hàm không thành viên chấp nhận một con trỏ đến một đối tượng và sau đó lưu nó trong các đối tượng, như vậy:Có điều gì sai khi sử dụng đối tượng trong cấu trúc của riêng mình không?

struct command 
{ 
    command() 
    { 
    } 

    command(const std::function<void()>& action) 
     : action(action) 
    { 
    } 

    int n; 
    std::function<void()> action; 
}; 

void test_action(command* this_ptr) 
{ 
    this_ptr->n = 5; 
} 


int main() 
{ 
    command com(std::bind(test_action, &com)); 
    com.action(); 
    std::cout << com.n; 
} 

Câu hỏi của tôi là nó an toàn để làm command com(std::bind(test_action, &com));? Hay là hành vi không xác định?

+0

Miễn là bạn không thực sự làm bất cứ điều gì với đối tượng trước khi nó được xây dựng, bạn là vàng. Và như @MarcoA đã nhận xét, bạn chỉ sử dụng địa chỉ trước thời điểm đó. – Deduplicator

+0

Có thể là một ý tưởng tốt để sao chép và di chuyển được bảo vệ nếu bạn định thực hiện việc này thường xuyên. –

Trả lời

7

Trước hết: đối tượng là gì?

[intro.object] \ 1

[...] Một đối tượng là một khu vực dung lượng lưu trữ [...]

Việc lưu trữ được phân bổ trước tuổi thọ của một đối tượng bắt đầu:

[basic.life]

Trước khi thời gian của đối tượng đã bắt đầu nhưng sau khi lưu trữ mà đối tượng sẽ chiếm được phân bổ [..] bất kỳ con trỏ nào đề cập đến vị trí lưu trữ nơi đối tượng đó hoặc nằm có thể được sử dụng chỉ theo những cách hạn chế. Đối với một đối tượng đang được xây dựng hoặc phá hủy, xem 12.7 [xây dựng và tiêu hủy]. Nếu không, con trỏ như vậy đề cập đến bộ nhớ được cấp phát (3.7.4.2) và sử dụng con trỏ như thể con trỏ có loại void *, được xác định rõ.

Do đó, con trỏ đề cập đến không gian được phân bổ và không có hại khi sử dụng nó. Bạn chỉ yêu cầu một địa chỉ ngăn xếp và bất kỳ trình biên dịch nào cũng có thể tìm ra chính xác. Không có hoạt động khởi tạo bởi chính đối tượng được yêu cầu trong trường hợp cụ thể này.

này có ý nghĩa vì trong một trình biên dịch AST-thời trang cổ điển nếu bạn để có một cái nhìn tại các hệ thống phân cấp tiêu chuẩn cho declarators, trong một món đồ chơi-mã đơn giản như

class command { 
public: 
    command(int) { 
    } 
}; 

int funct(command*) { 
    return 2; 
} 

int main() { 
    command com(funct(&com)); 
} 

dòng

command com(funct(&com)); 

được hiểu như sau:

[dcl.decl]

simple-declaration: 
    attribute-specifier-seqopt decl-specifier-seqopt init-declarator-listopt; 
     ... 
     initializer: 
      brace-or-equal-initializer 
       (expression-list) // The declaration statement is already specified 

Và cuối cùng cho mã của bạn đây là cách gcc biên dịch dòng này (-O0)

command com(std::bind(test_action, &com)); 

-> 

movq %rax, -104(%rbp) 
leaq -104(%rbp), %rdx 
leaq -96(%rbp), %rcx 
movl test_action(command*), %esi 
movq %rcx, %rdi 
movq %rax, -136(%rbp)  # 8-byte Spill 
movq %rcx, -144(%rbp)  # 8-byte Spill 
callq _ZSt4bindIRFvP7commandEJS1_EENSt12_Bind_helperIT_JDpT0_EE4typeEOS5_DpOS6_ 
leaq -80(%rbp), %rax 
movq %rax, %rdi 
movq -144(%rbp), %rsi  # 8-byte Reload 
movq %rax, -152(%rbp)  # 8-byte Spill 
callq _ZNSt8functionIFvvEEC1ISt5_BindIFPFvP7commandES5_EEEET_NSt9enable_ifIXntsr11is_integralISA_EE5valueENS1_8_UselessEE4typeE 
movq -136(%rbp), %rdi  # 8-byte Reload 
movq -152(%rbp), %rsi  # 8-byte Reload 
callq command::command(std::function<void()> const&) 

đó là: chỉ cần một loạt các địa chỉ ngăn xếp từ con trỏ cơ sở đó được truyền cho hàm ràng buộc trước khi gọi hàm tạo.

Mọi thứ sẽ khác nếu bạn thực sự cố gắng sử dụng đối tượng trước khi xây dựng (mọi thứ có thể gặp khó khăn với các bảng chức năng ảo).

Sidenote: đây là NOT đảm bảo an toàn nếu bạn đang sao chép hoặc chuyển giá trị đối tượng và đi ngoài phạm vi (và vẫn giữ địa chỉ cho vị trí ngăn xếp). Ngoài ra: nếu trình biên dịch quyết định để lưu trữ nó (cho bất kỳ kiến ​​trúc/lý do) như là một bù đắp từ khung cơ sở, có thể bạn đang đi đến hành vi không xác định.

+1

+1, mặc dù tranh luận từ sự kiện là một cách hay để hoàn toàn sai. – Deduplicator

+0

Tôi đồng ý và tôi thường không căn cứ bất kỳ quan sát nào từ "nó biên dịch" hoặc "nó hoạt động". Hành vi không xác định là một con thú khôn lanh. Dù sao thì tôi không thấy có hại trong trường hợp này. Nếu tôi bỏ lỡ một cái gì đó xin vui lòng cho tôi biết và tôi sẽ sửa bài viết –

+1

Tôi không chắc chắn như thế nào lắp ráp dump giúp với câu hỏi "an toàn" ... –

3

Có, bạn có thể sử dụng con trỏ đến đối tượng khi nó được khai báo (và do đó lưu trữ được phân bổ) nhưng chưa được khởi tạo. Truy cập đối tượng chính nó [ngoại trừ trong những cách rất hạn chế] cho hành vi không xác định; nhưng bạn không làm điều đó. Điều này được mô tả bởi C++ 11 3.8/5:

Trước khi đời của đối tượng đã bắt đầu nhưng sau khi lưu trữ mà đối tượng sẽ chiếm [...] bất kỳ con trỏ nào đề cập đến bộ nhớ vị trí mà đối tượng sẽ được đặt hoặc được định vị có thể được sử dụng nhưng chỉ theo những cách hạn chế. [...] Sử dụng con trỏ như thể con trỏ thuộc loại void*, được xác định rõ.

Bạn chỉ cần chuyển nó tới bind, sao chép giá trị con trỏ vào trình bao bọc chức năng bị ràng buộc, được tính là sử dụng nó như thể nó là void*.

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