2011-12-31 34 views
5

Nói rằng tôi có một chức năng như thế này:Sao chép chức năng tăng cường :: cũng có thể sao chép kết thúc?

void someFunction(const ExpensiveObjectToCopy&); 

Nếu tôi làm cho một tăng :: chức năng hiểu xem nó, chức năng đó sẽ lưu trữ bản sao nhân bản riêng của mình của đối tượng trong việc đóng cửa của nó:

boost::function<void()> f = boost::bind(someFunction, x); // <-- f saves a copy of x 

Bây giờ nếu tôi bắt đầu đi qua xung quanh, sẽ tăng :: chức năng sao chép constructor sao chép đối tượng đó một lần nữa, hoặc hiện mỗi chức năng chia sẻ cùng một đóng cửa? (Ví dụ như thế này)

boost::function<void()> f2 = f; 
callSomeFunction(f); 
etc. 

Trả lời

4

Từ những gì tôi có thể tìm thấy (Đánh giá từ một đọc lướt qua các nguồn không chính xác đơn giản và một số thí nghiệm) nó sẽ sao chép các đối tượng nhân bản mỗi lần. Nó có thể là không cần thiết trong trường hợp hàm lấy đối số của nó bằng const &, nhưng nói chung đối tượng có thể bị đột biến bởi hàm. Nếu đối tượng là tốn kém để sao chép, nó sẽ không có ý nghĩa để nắm bắt nó bằng cách tham khảo (boost::ref hoặc boost::cref đến) hoặc, nếu đối tượng ban đầu không tồn tại tại thời điểm gọi, chụp boost::shared_ptr và viết một bộ điều hợp phương pháp, trong đó unpacks các smartpointer và các cuộc gọi someFunction?

Chỉnh sửa: Từ thử nghiệm không chỉ nó sẽ sao chép xây dựng đối tượng đó bất cứ khi nào boost::function được sao chép, nhưng nó cũng sẽ sao chép nhiều lần bên trong boost::bind. Tôi đã thử nghiệm bằng cách sử dụng đoạn mã sau sử dụng tăng 1,45 dưới mingw 32 với gcc 4.6 và -O2 (và -std = C++ 0x):

struct foo_bar { 
    std::vector<int> data; //possibly expensive to copy 
    foo_bar() 
    { std::cout<<"default foo_bar "<<std::endl; } 
    foo_bar(const foo_bar& b):data(b.data) 
    { std::cout<<"copy foo_bar "<<&b<<" to "<<this<<std::endl; } 
    foo_bar& operator=(const foo_bar& b) { 
     this->data = b.data; 
     std::cout<<"asign foo_bar "<<&b<<" to "<<this<<std::endl; 
     return *this; 
    } 
    ~foo_bar(){} 
}; 

void func(const foo_bar& bar) { std::cout<<"func"<<std::endl;} 

int main(int, char*[]) { 
    foo_bar fb; 
    boost::function<void()> f1(boost::bind(func, fb)); 
    std::cout<<"Bind finished"<<std::endl; 
    boost::function<void()> f2(f1); 
    std::cout<<"copy finished"<<std::endl; 
    f1(); 
    f2(); 
    return 0; 
} 

Sản lượng kết quả được như sau:

default foo_bar 
copy foo_bar 0x28ff00 to 0x28ff10 
copy foo_bar 0x28ff10 to 0x28ff28 
copy foo_bar 0x28ff28 to 0x28ff1c 
copy foo_bar 0x28ff1c to 0x28ff34 
copy foo_bar 0x28ff34 to 0x28fed4 
copy foo_bar 0x28fed4 to 0x28fee4 
copy foo_bar 0x28fee4 to 0x28fef4 
copy foo_bar 0x28fef4 to 0x28fe14 
copy foo_bar 0x28fe14 to 0x28fe24 
copy foo_bar 0x28fe24 to 0x28fe34 
copy foo_bar 0x28fe34 to 0x6a2c7c 
Bind finished 
copy foo_bar 0x6a2c7c to 0x6a2c94 
copy finished 
func 
func 

Vì vậy, các nhà xây dựng bản sao được gọi là cho việc tạo ra các f2 một lần và 11 lần cho các ràng buộc và chuyển nhượng cho f1. Vì đối tượng đầu tiên được tạo trên ngăn xếp và địa chỉ của các bản sao rất gần và tăng nhẹ, có vẻ như quá trình liên kết đi qua rất nhiều hàm, trình biên dịch không trực tuyến trong trường hợp này và mỗi chuyển đối tượng theo giá trị. Chỉ sử dụng boost::bind mà không lưu kết quả ở bất kỳ đâu:

int main(int, char*[]) { 
    foo_bar fb; 
    boost::function<void()> f1(boost::bind(func, fb)); 
    return 0; 
} 

default foo_bar 
copy foo_bar 0x28ff00 to 0x28ff10 
copy foo_bar 0x28ff10 to 0x28ff28 
copy foo_bar 0x28ff28 to 0x28ff1c 
copy foo_bar 0x28ff1c to 0x28ff34 
copy foo_bar 0x28ff34 to 0x28fef4 

Vì vậy, năm bản sao chỉ để ràng buộc đối tượng. Vì vậy, tôi thường sẽ tránh chụp bất kỳ thứ gì có ít nhất chi phí bản sao vừa phải cho mỗi giá trị trong bất kỳ bộ phận nhạy cảm hiệu suất từ ​​xa nào của mã. Trong GCC so std::tr1::bindstd::bind thực hiện tốt hơn nhiều (kết hợp với std :: tr1 :: chức năng/std :: chức năng) (mã về cơ bản là giống với testcode đầu tiên, chỉ cần thay boost:: với std::tr1:: tương ứng std:::

std::tr1::bind with std::tr1::function: 
default foo_bar 
copy foo_bar 0x28ff10 to 0x28ff28 
copy foo_bar 0x28ff28 to 0x28ff34 
copy foo_bar 0x28ff34 to 0x28ff04 
copy foo_bar 0x28ff04 to 0x652c7c 
Bind finished 
copy foo_bar 0x652c7c to 0x652c94 
copy finished 
func 
func 

std::bind with std::function: 
default foo_bar 
copy foo_bar 0x28ff34 to 0x28ff28 
copy foo_bar 0x28ff28 to 0x3c2c7c 
Bind finished 
copy foo_bar 0x3c2c7c to 0x3c2c94 
copy finished 
func 
func 

Tôi giả sử std::bind hoặc chuyển qua const ref cho invocations nội bộ hoặc được viết theo cách thân thiện hơn với gccs inliner để nội tuyến một số và loại bỏ các nhà xây dựng sao chép dư thừa.

Tất nhiên là luôn luôn với một loại thử nghiệm YMMV với các biên dịch/biên dịch khác nhau

+0

Tôi đã viết một chương trình thử nghiệm và có vẻ như các nhà xây dựng bản sao của đối tượng được lưu trữ không được gọi mỗi khi bạn sao chép các đối tượng chức năng . Ngoài ra, boost :: bind gọi hàm tạo bản sao 11 lần! – Chris

+0

@ Chris: Ok, vì vậy bài kiểm tra của tôi không phải là một điều tốt để biết. Vì vậy, nếu người ta có thể sử dụng c + + 11 có vẻ như std :: bind là cách để đi bằng một lề xa (mặc dù cá nhân tôi sẽ chỉ sử dụng lambdas thay thế). – Grizzly

3

Nếu bạn chuyển đối tượng theo giá trị sang bind, nó sẽ được sao chép (như bạn đã kiểm tra: 11 lần).

Nhưng nếu bạn không muốn sao chép, hãy chuyển nó theo tham chiếu (sử dụng boost::cref) và không được sao chép.

struct a 
{ 
    a() { std::cout << __func__ << std::endl; } 
    a(const a &) { std::cout << __func__ << std::endl; } 
    ~a() { std::cout << __func__ << std::endl; } 
    const a & operator=(const a & aa) 
    { std::cout << __func__ << std::endl; return aa; } 
}; 

void g(const a &) { std::cout << __func__ << std::endl; } 


void t2() 
{ 
    a aa; 

    boost::function< void() > ff = boost::bind(g, boost::cref(aa)); 
    boost::function< void() > ff2 = ff; 
    std::cout << "after ff" << std::endl; 
    ff(); 
    ff2(); 
    std::cout << "after called ff()" << std::endl; 
} 

đầu ra:

a 
after ff 
g 
g 
after called ff() 
~a 

Đó là

  1. một constructor gọi khi đối tượng được tạo
  2. không có constructor được gọi khi tạo chức năng đối tượng ff hoặc thực hiện một bản sao của nó (ff2)
  3. không có hàm tạo gọi khi g(const a &) gọi qua ff() hoặc ff2()
  4. destructor được gọi khi đối tượng đi ra khỏi phạm vi
Các vấn đề liên quan