【问题标题】:Is explicitly calling constructors and destructors safe, according to the C++ standard?根据 C++ 标准,显式调用构造函数和析构函数是否安全?
【发布时间】:2016-06-12 04:31:13
【问题描述】:

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

例如,在这篇文章中,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;
    }
};

对于像这样的某些场景,可能需要显式调用构造函数和析构函数,但标准是怎么说的:它是未定义的行为,是未指定的行为,是实现定义的行为,还是定义良好?

【问题讨论】:

  • 您只需要确保每个对象构造一次并销毁一次
  • 请注意,严格来说,您不能“调用构造函数”。构造函数没有名字。它们在对象初始化期间被调用。初始化对象的一种方法是使用newnew 的一种语法是placement new,它在给定位置构造一个对象。

标签: c++ constructor standards destructor


【解决方案1】:

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

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

称为placement new syntax,用于在specific memory location处构造对象,默认行为;如要求在分配器张贴。如果针对特定类型T 重载了placement new,则将调用它而不是全局placement new。

破坏这种构造对象的唯一方法是explicitly call the destructorp-&gt;~T();

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

【讨论】:

  • 注意:它是有效且允许的,当然;但是它应该只出现在分配器/容器中。
  • @MatthieuM。歧视工会是容器吗?可选?一个最大固定大小的自动存储vector-like? (不符合标准的容器) SFO function-like?它可能应该只出现在低级、谨慎的代码中,但只有“分配器/容器”在推动它。
  • @Yakk:我认为optionalvariant 和 max-fixed-sized 是容器,是的,因为它们的唯一作用是包含其他对象。不过不知道 SFO 是什么。
  • @MatthieuM。 -- 一个通用的函数对象,基本上(它已经足够容器化了)
  • @matt C++ 标准说明了容器的含义。如果您将容器定义为“包含某些东西的对象”,我不确定什么不符合容器的条件。 SFO 是小函数优化(如更著名的 SSO):一般来说,当类型擦除到值语义时,您可能需要 smallmobject 优化,这需要手动 ctor/dtors。所有类型的橡皮擦都是容器吗?
【解决方案2】:

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

更准确地说,标准容器模板有一个Allocator 模板参数,默认为std::allocatorstd::allocator 在其allocate 成员函数中使用placement new。

例如,这是允许 std::vector 实现 push_back 的原因,这样内存分配不必一直进行,而是在当前容量不再足够时分配一些额外的内存,准备用 future push_backs 添加的元素的空间。

这意味着当你在循环中调用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.:仅仅因为标准容器内部使用placement new,这并不意味着您应该在自己的代码中疯狂使用它。它一种低级技术,如果你不实现自己的通用数据结构,因为没有标准容器提供你需要的功能,你可能永远找不到它的任何用途。 p>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-10-23
    • 1970-01-01
    • 1970-01-01
    • 2011-04-03
    • 2010-12-16
    • 2015-07-27
    • 1970-01-01
    相关资源
    最近更新 更多