根据C ++标准,显式调用构造函数和析构函数是否安全?

时间:2016-02-29 07:55:26

标签: c++ constructor standards destructor

一些开发人员明确地调用构造函数和析构函数来解决某些问题。我知道,这不是一个好习惯,但似乎是为了实现一些场景。

例如,在本文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;
    }
};

对于像这样的一些场景,可能需要显式调用构造函数和析构函数,但标准说的是:它是未定义的行为,是未指定的行为,是实现定义的行为,还是定义得很好?< / p>

2 个答案:

答案 0 :(得分:21)

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

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

称为placement new syntax,用于构造specific memory location的对象,默认行为;如发布的分配器中所需的。如果针对特定类型T重载了展示位置new,则会调用它而不是全局展示位置。

破坏此类构造对象的唯一方法是explicitly call the destructor p->~T();

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

答案 1 :(得分:8)

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

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

例如,这是允许std::vector实现push_back以便内存分配不必一直发生的内容,而是每当当前容量为no时分配一些额外的内存更长的时间,为 future 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提供一个解决方案,包括没有默认构造函数的类型。这是placement new的强大功能:首先分配内存,然后使用手动构造函数调用将元素放在分配的内存中。

作为旁注,new[]所有这些都是不可能的。事实上,new[]是一种非常无用的语言功能。

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