2016-02-29 30 views
13

某些开发人员显式调用构造函数和析构函数以获取某些解决方法。我知道,这不是一个好的做法,但似乎是为了实现某些场景。根据C++标准,显式调用构造函数和析构函数是安全的吗?

例如,在本文中,Beautiful Native Libraries,作者使用这种技术。

在下面的代码,到了最后,可以看出,构造函数是显式调用:

#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; 
    } 
}; 

对于一些像这样的情况下,可能有必要调用构造函数和析构函数明确,但什么标准是否会说:它是未定义的行为,它是未指定的行为,是实现定义的行为还是定义良好?

+1

您只需确保每个对象构造一次并销毁一次 –

+0

请注意,严格来说,您无法“调用构造函数”。构造函数没有名字。它们在对象初始化期间被调用。初始化对象的一种方法是使用'new'。 'new'的一种语法是placement new,它在给定的位置构造一个对象。 – isanae

回答

21

是的,它是支持和明确的,它是安全的。

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

正所谓placement new syntax和用于以specific memory location,默认行为来构建的对象;如发布的分配器中的要求。如果展示位置new是针对特定类型T超载的,那么它将被调用而不是新的全局展示位置。

破坏这种构造物的唯一方法是explicitly call the destructorp->~T();

使用放置新的和显式的销毁确实需要/允许实现的代码控制对象的生命周期 - 编译器在这种情况下提供的帮助很少;因此重要的是物体在良好对齐和充分分配的位置上构建。它们的使用经常在分配器中找到,例如OP中的和std::allocator

+3

注意:这当然是有效的和允许的;但它应该只能出现在分配器/容器中。 –

+3

@MatthieuM。区分工会容器?选配?一个最大固定大小的自动存储'矢量'样? (不符合该标准的容器)SFO'功能'样?它应该只发生在低层次,谨慎的代码中,但只有“分配器/容器”才会推动它。 – Yakk

+2

@Yakk:我认为'optional','variant'和max-fixed-sized是容器是的,因为它们唯一的作用是包含其他对象。不知道SFO是什么。 –

8

是的,它是完全安全的。事实上,默认情况下,所有标准容器(如std::vector)都使用技术,因为它是将内存分配与元素构造分开的唯一方法。

更确切地说,标准容器模板的Allocator模板参数默认为std::allocator,并且std::allocator在其allocate成员函数中使用了新的位置。

例如,这允许std::vector实施push_back,使得存储器分配不必一直发生,而是在当前容量不再足够时分配一些额外的存储器,为元件准备空间加上未来push_back s。

这意味着,当你在一个循环中调用push_back一百倍,std::vector实际上是足够聪明不是每一次,这有助于提高性能,因为重新分配,并把现有的容器内容到一个新的存储位置是昂贵的分配内存。

例子:

#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"; 
    } 
} 

输出:

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 

不过,当然,你不想调用构造函数潜在未来元素。对于int这并不重要,但我们需要每个T的解决方案,包括没有默认构造函数的类型。这是放置new的强大功能:先分配内存,然后使用手动构造函数调用将元素放入分配的内存中。


作为一个附注,所有这一切都不可能与new[]。实际上,new[]是一个非常无用的语言功能。


P.S .:只是因为标准容器内部使用放置新的,这并不意味着你应该在你自己的代码中使用它。它的低级技术,如果你没有实现你自己的通用数据结构,因为没有标准容器提供你需要的功能,你可能永远找不到它的任何用处。

相关问题