2010-04-18 27 views
5

Làm thế nào để ngăn chặn việc khởi tạo và hủy kiểu tự động? Trong khi nó là tuyệt vời mà T buffer[100] tự động khởi tạo tất cả các yếu tố của buffer, và phá hủy chúng khi chúng rơi ra khỏi phạm vi, đây không phải là hành vi tôi muốn.C++ Suppress Automatic Initialization and Destruction

#include <iostream> 

static int created = 0, 
      destroyed = 0; 

struct S 
{ 
    S() 
    { 
     ++created; 
    } 
    ~S() 
    { 
     ++destroyed; 
    } 
}; 

template <typename T, size_t KCount> 
class fixed_vector 
{ 
private: 
    T m_buffer[KCount]; 
public: 
    fixed_vector() 
    { 
     // some way to suppress the automatic initialization of m_buffer 
    } 

    ~fixed_vector() 
    { 
     // some way to suppress the automatic destruction of m_buffer 
    } 
}; 

int main() 
{ 
    { 
     fixed_vector<S, 100> arr; 
    } 

    std::cout << "Created:\t" << created << std::endl; 
    std::cout << "Destroyed:\t" << destroyed << std::endl; 
    return 0; 
} 

Kết quả của chương trình này là:

Created: 100 
Destroyed: 100 

Tôi muốn nó là:

Created: 0 
Destroyed: 0 

ý tưởng duy nhất của tôi là làm cho m_buffer một số trivially xây dựng và destructed loại như char và sau đó dựa vào operator[] để bọc toán con trỏ cho tôi, mặc dù điều này có vẻ giống như một giải pháp bị tấn công khủng khiếp. Một giải pháp khác là sử dụng mallocfree, nhưng điều đó mang lại một mức độ vô hướng mà tôi không muốn.


Lý do tôi muốn điều này là vì tôi đang tạo thùng chứa và tôi không muốn trả tiền cho phí khởi tạo của những thứ mà tôi sẽ không sử dụng. Ví dụ, nếu chức năng main của tôi là:

int main() 
{ 
    { 
     std::vector<S> vec; 
     vec.reserve(50); 
    } 

    std::cout << "Created:\t" << created << std::endl; 
    std::cout << "Destroyed:\t" << destroyed << std::endl; 
    return 0; 
} 

Kết quả sẽ chính xác:

Created: 0 
Destroyed: 0 
+0

Tại sao bạn muốn một mảng các phiên bản 'S' không bị cấu trúc? Bạn có thể sử dụng chúng để làm gì? –

+0

Không có gì - đó là vấn đề. Tôi đã sửa đổi câu hỏi của mình bằng cách biện minh. –

+0

Vì vậy, nếu tôi hiểu bạn một cách chính xác, bạn muốn dự trữ không gian trong vectơ của mình cho các phần tử bạn sẽ điền vào sau và bạn không muốn trả tiền cho việc xây dựng (và hủy) các phần tử hai lần. Sự khác biệt về hiệu suất này có thực sự quan trọng không? Bạn đã lược tả ứng dụng của mình và xác minh rằng đây là nơi được dành nhiều thời gian nhất trong khi thực hiện? –

Trả lời

3

Bạn có thể tạo mảng dưới dạng mảng char s rồi sử dụng placement new để tạo các phần tử khi cần.

template <typename T, size_t KCount> 
class Array 
{ 
private: 
    char m_buffer[KCount*sizeof(T)]; // TODO make sure it's aligned correctly 

    T operator[](int i) { 
     return reinterpret_cast<T&>(m_buffer[i*sizeof(T)]); 
    } 

Sau khi đọc câu hỏi của bạn có vẻ như bạn muốn có một mảng thưa thớt, điều này đôi khi trôi qua tên của đồ; o) (tất nhiên các đặc tính hiệu suất khác nhau ...)

template <typename T, size_t KCount> 
class SparseArray { 
    std::map<size_t, T> m_map; 
public: 
    T& operator[](size_t i) { 
     if (i > KCount) 
      throw "out of bounds"; 
     return m_map[i]; 
    } 
+1

Tôi đã suy nghĩ 'reinterpret_cast', vì ngữ nghĩa có ý nghĩa hơn với tôi. Nhưng tôi đã thực sự hy vọng để tránh điều này - có cách nào để không phải làm điều này? –

+0

Bạn nói đúng, 'static_cast' là lựa chọn sai, cập nhật câu trả lời của tôi. – Motti

+0

Giải pháp đầu tiên của bạn là những gì tôi đang tìm kiếm và về cơ bản chính xác là những gì tôi muốn. –

0

Bạn có thể có một cái nhìn vào cách nó được thực hiện với các container STL, nhưng tôi nghi ngờ bạn có thể tha mình malloc s và free s

+0

Tôi có thể tha thứ cho mình 'malloc' và' free' bằng cách sử dụng 'char m_buffer [KCount * sizeof (T)]' (mặc dù rõ ràng với các ngữ nghĩa khác nhau, vì điều đó không được phép). –

1

mã này:

#include <iostream> 
#include <vector> 
using namespace std; 

int created = 0, destroyed = 0; 

struct S 
{ 
    S() 
    { 
     ++created; 
    } 
    S(const S & s) { 
     ++created; 
    } 
    ~S() 
    { 
     ++destroyed; 
    } 
}; 

int main() 
{ 
    { 
     std::vector<S> vec; 
     vec.reserve(50); 
    } 

    std::cout << "Created:\t" << created << std::endl; 
    std::cout << "Destroyed:\t" << destroyed << std::endl; 
    return 0; 
} 

có chính xác kết quả bạn muốn - Tôi không chắc câu hỏi của bạn là gì.

+0

anh ta không thể làm 'vec [1] 'sau đó. (lười biếng xây dựng) –

+0

Đó là chính xác (như đã nêu trong câu hỏi của tôi), nhưng làm thế nào để 'std :: vector' làm điều này? Nhìn qua thực hiện của g ++, nó * có vẻ * giống như họ làm một số 'reinterpret_cast' trên một 'char []' để tránh khởi tạo cho đến giây cuối cùng. –

+0

@Travis vector tạo bộ nhớ chưa được khởi tạo và sau đó sử dụng vị trí mới để tạo các đối tượng thực trong đó khi cần. –

4

Bạn có thể muốn xem xét boost::optional

template <typename> struct tovoid { typedef void type; }; 

template <typename T, size_t KCount, typename = void> 
struct ArrayStorage { 
    typedef T type; 
    static T &get(T &t) { return t; } 
}; 

template <typename T, size_t KCount> 
struct ArrayStorage<T, KCount, typename tovoid<int T::*>::type> { 
    typedef boost::optional<T> type; 
    static T &get(boost::optional<T> &t) { 
    if(!t) t = boost::in_place(); 
    return *t; 
    } 
}; 

template <typename T, size_t KCount> 
class Array 
{ 
public: 
    T &operator[](std::ptrdiff_t i) { 
     return ArrayStorage<T, KCount>::get(m_buffer_[i]); 
    } 

    T const &operator[](std::ptrdiff_t i) const { 
     return ArrayStorage<T, KCount>::get(m_buffer_[i]); 
    } 

    mutable typename ArrayStorage<T, KCount>::type m_buffer_[KCount]; 
}; 

Một chuyên môn được thực hiện cho loại lớp kết thúc chúng thành một optional, do đó gọi hàm tạo/destructor lazily. Đối với các loại không phải lớp, chúng tôi không cần gói đó. Không gói chúng có nghĩa là chúng ta có thể xử lý &a[0] như một vùng bộ nhớ liền kề và chuyển địa chỉ đó tới các hàm C muốn một mảng. boost::in_place sẽ tạo các loại lớp tại chỗ, mà không cần sử dụng tạm thời T hoặc hàm tạo bản sao của lớp.

Không sử dụng thừa kế hoặc thành viên private cho phép các lớp ở một số tổng hợp, cho phép một hình thức thuận tiện để khởi

// only two strings are constructed 
Array<std::string, 10> array = { a, b }; 
+0

Đây thực sự là điều đầu tiên tôi nhìn vào. Tuy nhiên, một 'boost :: optional ' có thêm một cờ để cho nó khả năng sử dụng toán tử 'bool()', do đó, một 'lazy_array ' sẽ không cần thiết chiếm gấp đôi không gian cần thiết. –

+0

@Travis hoặc là bạn đi cho tốc độ, hoặc bạn đi cho không gian. Tôi không nghĩ rằng điều này có thể được giải quyết mà không có một lá cờ. Làm thế nào bạn sẽ xem liệu 'm_buffer [i]' chưa được xây dựng chưa? Phần tử được đặt vào vị trí đó có thể gây ra bất kỳ sự kết hợp nào của các bit, vì vậy bạn không thể xác định trạng thái đó bằng các phương tiện thuần túy của ô nhớ đó. Nhưng thực sự, đó là onyly một 'bool' nó taks. Nếu bạn đi cho không gian, bạn có thể phân bổ một mảng 'T *' và khởi tạo nó thành các con trỏ rỗng, và sau đó sử dụng 'new' để phân bổ các phần tử của bạn. Tuy nhiên tôi thậm chí không chắc chắn rằng điều này sẽ sử dụng ít không gian phía sau hậu trường để quản lý heap. –

+0

Tôi đoán tôi có thể đã chỉ định trong câu hỏi ban đầu của mình, nhưng trong trường hợp này tôi có được tốt nhất của cả hai thế giới vì nó là một container. Trên 'push_back', tôi có thể coi phần tử là uninitialized và' pop_back' tôi có thể xử lý phần tử như được khởi tạo. Vì vậy, mọi thứ '[0, size())' được khởi tạo và '[size(), KCount)' được đơn vị hóa. –

1

Nếu bạn muốn được như vector, bạn nên làm một cái gì đó như thế này:

template <typename T> 
class my_vector 
{ 
    T* ptr; // this is your "array" 
    // ... 
    public: 

    void reserve(size_t n) 
    { 
     // allocate memory without initializing, you could as well use malloc() here 
     ptr = ::operator new (n*sizeof(T)); 
    } 

    ~my_vector() 
    { 
     ::operator delete(ptr); // and this is how you free your memory 
    } 

    void set_element(size_t at, const T& element = T()) 
    { 
     // initializes single element 
     new (&ptr[at]) T(element); // placement new, copies the passed element at the specified position in the array 
    } 

    void destroy_element(size_t at) 
    { 
     ptr[at].~T(); // explicitly call destructor 
    } 
}; 

Mã này rõ ràng chỉ dành cho trình diễn, tôi đã bỏ qua trình tạo bản sao của my_vector và bất kỳ việc theo dõi nào được tạo ra và những gì không (gọi destructor trên một vị trí mà bạn chưa gọi là hàm tạo có thể là hành vi không xác định). Ngoài ra, phân bổ vector của STL và deallocations được abstracted đi qua việc sử dụng các phân bổ (các đối số mẫu thứ hai của vector).

Hy vọng rằng sẽ giúp