2016-02-29 17 views
13

Một số nhà phát triển gọi cho nhà thầu và người phá hủy một cách rõ ràng đối với một số cách giải quyết. Tôi biết, nó không phải là một thực hành tốt, nhưng có vẻ như nó được thực hiện để nhận ra một số kịch bản.Được gọi một cách rõ ràng các nhà xây dựng và phá hủy an toàn, theo tiêu chuẩn C++?

Ví dụ: trong bài viết này, Beautiful Native Libraries, tác giả sử dụng kỹ thuật này.

Trong đoạn mã dưới đây, ở cuối, nó có thể được nhìn thấy rằng các nhà xây dựng được gọi một cách rõ ràng:

#include <limits> 

template <class T> 
struct proxy_allocator { 
    typedef size_t size_type; 
    typedef ptrdiff_t difference_type; 
    typedef T *pointer; 
    typedef const T *const_pointer; 
    typedef T& reference; 
    typedef const T &const_reference; 
    typedef T value_type; 

    template <class U> 
    struct rebind { 
     typedef proxy_allocator<U> other; 
    }; 

    proxy_allocator() throw() {} 
    proxy_allocator(const proxy_allocator &) throw() {} 
    template <class U> 
    proxy_allocator(const proxy_allocator<U> &) throw() {} 
    ~proxy_allocator() throw() {} 

    pointer address(reference x) const { return &x; } 
    const_pointer address(const_reference x) const { return &x; } 

    pointer allocate(size_type s, void const * = 0) { 
     return s ? reinterpret_cast<pointer>(yl_malloc(s * sizeof(T))) : 0; 
    } 

    void deallocate(pointer p, size_type) { 
     yl_free(p); 
    } 

    size_type max_size() const throw() { 
     return std::numeric_limits<size_t>::max()/sizeof(T); 
    } 

    void construct(pointer p, const T& val) { 
     new (reinterpret_cast<void *>(p)) T(val); 
    } 

    void destroy(pointer p) { 
     p->~T(); 
    } 

    bool operator==(const proxy_allocator<T> &other) const { 
     return true; 
    } 

    bool operator!=(const proxy_allocator<T> &other) const { 
     return false; 
    } 
}; 

Đối với một số tình huống như thế này, nó có thể là cần thiết để gọi constructor và destructor một cách rõ ràng, nhưng những gì không tiêu chuẩn nói: là nó không xác định hành vi, là nó không xác định hành vi, là nó thực hiện xác định hành vi, hoặc là nó cũng được xác định?

+1

Bạn chỉ cần đảm bảo rằng mỗi đối tượng được xây dựng một lần và bị hủy một lần –

+0

Lưu ý rằng nói đúng, bạn không thể "gọi hàm tạo". Các nhà xây dựng không có tên. Chúng được gọi trong khi khởi tạo một đối tượng. Một cách để khởi tạo một đối tượng là sử dụng 'new'. Một cú pháp của 'mới' là vị trí mới, cấu trúc một đối tượng tại một vị trí nhất định. – isanae

Trả lời

21

Có hỗ trợ và được xác định rõ, nó an toàn.

new (reinterpret_cast<void *>(p)) T(val); 

Được gọi là placement new syntax và được sử dụng để xây dựng một đối tượng tại một specific memory location, hành vi mặc định; chẳng hạn như được yêu cầu trong bộ cấp phát được đăng. Nếu vị trí mới bị quá tải cho loại cụ thể T, thì vị trí đó sẽ được gọi thay vì vị trí toàn cầu mới.

Cách duy nhất để hủy một đối tượng xây dựng như vậy là để explicitly call the destructorp->~T();.

Việc sử dụng vị trí hủy diệt mới và rõ ràng đòi hỏi/cho phép mã thực hiện kiểm soát tuổi thọ của đối tượng - trình biên dịch cung cấp ít trợ giúp trong trường hợp này; do đó, điều quan trọng là các đối tượng được xây dựng ở vị trí phù hợp và được phân bổ đầy đủ. Việc sử dụng chúng thường được tìm thấy trong các trình phân bổ, chẳng hạn như trong OP và std::allocator.

+3

Lưu ý: nó hợp lệ và được cho phép, chắc chắn; tuy nhiên nó có lẽ chỉ xuất hiện trong các trình phân bổ/thùng chứa. –

+3

@MatthieuM. Các thùng chứa công đoàn phân biệt đối xử? Tùy chọn? Tự động lưu trữ 'vector' có kích thước cố định tối đa như thế nào? (không đủ điều kiện như là một container theo tiêu chuẩn) Một SFO 'chức năng' giống như? Nó có lẽ chỉ nên xảy ra ở mức độ thấp, mã cẩn thận, nhưng chỉ "cấp phát/container" đang đẩy nó. – Yakk

+2

@Yakk: Tôi xem xét 'tùy chọn',' biến thể' và kích thước tối đa cố định là các vùng chứa có, vì vai trò duy nhất của chúng là chứa các đối tượng khác. Không biết SFO là gì. –

8

Có, nó hoàn toàn an toàn. Như một vấn đề của thực tế, tất cả các container tiêu chuẩn tiêu chuẩn như std::vector sử dụng kỹ thuật theo mặc định, bởi vì nó là cách duy nhất để phân bổ bộ nhớ riêng biệt từ xây dựng phần tử.

Chính xác hơn, các mẫu chứa chuẩn có đối số mẫu Allocator mặc định là std::allocatorstd::allocator sử dụng vị trí mới trong chức năng thành viên allocate của nó. Ví dụ:

Ví dụ, điều này cho phép std::vector thực hiện push_back sao cho phân bổ bộ nhớ không phải luôn xảy ra, mà thay vào đó bộ nhớ bổ sung được cấp phát bất cứ khi nào dung lượng hiện tại không còn đủ. được thêm với tương laipush_back s.

Điều này có nghĩa là khi bạn gọi push_back một trăm lần trong vòng lặp, std::vector thực sự đủ thông minh để không cấp phát bộ nhớ mỗi lần, giúp hiệu suất vì phân bổ lại và di chuyển nội dung vùng chứa hiện tại sang vị trí bộ nhớ mới rất tốn kém.

Ví dụ:

#include <vector> 
#include <iostream> 

int main() 
{ 
    std::vector<int> v; 

    std::cout << "initial capacity: " << v.capacity() << "\n"; 

    for (int i = 0; i < 100; ++i) 
    { 
     v.push_back(0); 

     std::cout << "capacity after " << (i + 1) << " push_back()s: " 
      << v.capacity() << "\n"; 
    } 
} 

Output:

initial capacity: 0 
capacity after 1 push_back()s: 1 
capacity after 2 push_back()s: 2 
capacity after 3 push_back()s: 3 
capacity after 4 push_back()s: 4 
capacity after 5 push_back()s: 6 
capacity after 6 push_back()s: 6 
capacity after 7 push_back()s: 9 
capacity after 8 push_back()s: 9 
capacity after 9 push_back()s: 9 
capacity after 10 push_back()s: 13 
capacity after 11 push_back()s: 13 
capacity after 12 push_back()s: 13 
capacity after 13 push_back()s: 13 
capacity after 14 push_back()s: 19 

(...)

capacity after 94 push_back()s: 94 
capacity after 95 push_back()s: 141 
capacity after 96 push_back()s: 141 
capacity after 97 push_back()s: 141 
capacity after 98 push_back()s: 141 
capacity after 99 push_back()s: 141 
capacity after 100 push_back()s: 141 

Nhưng tất nhiên, bạn không muốn gọi một constructor cho các yếu tố tiềm năng trong tương lai. Đối với int nó sẽ không quan trọng, nhưng chúng tôi cần một giải pháp cho mọi T, bao gồm các loại không có hàm tạo mặc định. Đây là sức mạnh của vị trí mới: cấp phát bộ nhớ trước, sau đó đặt các phần tử trong bộ nhớ được cấp phát sau, bằng cách sử dụng cuộc gọi hàm tạo thủ công.


Lưu ý phụ, tất cả điều này là không thể với new[]. Thực tế, new[] là một tính năng ngôn ngữ khá vô dụng.


P .: Chỉ vì các thùng chứa tiêu chuẩn sử dụng nội bộ vị trí mới, điều đó không có nghĩa là bạn nên đi hoang dã với mã trong mã của riêng bạn. Nó một kỹ thuật cấp thấp và nếu bạn không triển khai cấu trúc dữ liệu chung của riêng mình vì không có vùng chứa chuẩn nào cung cấp chức năng bạn cần, bạn không bao giờ có thể tìm thấy bất kỳ việc sử dụng nào cho nó.

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