2016-02-29 3 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' - это размещение new, которое строит объект в определенном месте. – isanae

ответ

21

Да, он поддерживается и четко определен, он безопасен.

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

Называется placement new syntax и используется для создания объекта на specific memory location, поведение по умолчанию; как это требуется в размещенном распределителе. Если новое место размещения перегружено для определенного типа T, оно будет вызываться вместо глобального размещения нового.

Единственный способ разрушить такой объект - explicitly call the destructorp->~T();.

Использование места размещения нового и явного уничтожения требует/допускает, что реализованный код управляет временем жизни объекта - в этом случае компилятор мало помогает; поэтому важно, чтобы объекты были построены в хорошо выровненных и достаточно распределенных местах. Их использование часто встречается в распределителях, например, в OP и std::allocator.

+3

Примечание: оно действительно и разрешено, конечно; однако он, вероятно, должен появиться только в распределителях/контейнерах. –

+3

@MatthieuM. Являются ли дискриминационные союзы контейнерами? Optionals? Авто-хранилище с максимальным фиксированным размером 'vector'-like? (не может считаться контейнером по стандарту) SFO 'function'-like? Вероятно, это должно происходить только на низком уровне, тщательный код, но только «распределители/контейнеры» его подталкивают. – Yakk

+2

@Yakk: Я считаю, что «необязательный», «вариант» и максимальный фиксированный размер являются контейнерами «да», поскольку их единственная роль состоит в том, чтобы содержать другие объекты. Не знаю, что такое SFO. –

8

Да, это абсолютно безопасно. По сути, все стандартные контейнеры , такие как std::vector, используют технику по умолчанию, поскольку это единственный способ разделить выделение памяти от конструкции элемента.

Более точно, шаблоны стандартных контейнеров имеют Allocator аргумент шаблона, который по умолчанию std::allocator и std::allocator использует размещение нового в своей функции allocate члена.

Это, например, то, что позволяет std::vector реализовать push_back так, что распределение памяти не должно происходить все время, но вместо этого выделяется дополнительная память, когда текущая емкость больше не достаточна, подготовка пространства для элементов Добавлено futurepush_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 .: Просто потому, что стандартные контейнеры внутри использовать размещение нового, это не значит, что вы должны пойти диким с ним в собственном коде. Это является низкоуровневой техникой, и если вы не реализуете свою собственную общую структуру данных, потому что ни один стандартный контейнер не предоставляет необходимые вам функциональные возможности, вы никогда не сможете использовать его вообще.

 Смежные вопросы

  • Нет связанных вопросов^_^