boost :: make_shared не является оператором вызова (размещения) new? - PullRequest
6 голосов
/ 13 марта 2012

Я впервые использую boost :: make_shared для создания объектов, на которые указывают общие указатели. Главным образом потому, что наш код был слишком медленным и одиночное распределение действительно помогло улучшить производительность.

После исправления некоторых утечек памяти «сложным ручным способом» я решил реализовать простой детектор утечек памяти, переопределив новые операторы для всех соответствующих классов, просто чтобы подсчитать, какие объекты еще живы в определенных точках в нашем приложении. Я реализовывал это несколько раз и был удивлен, обнаружив, что мой код больше не обнаруживает никаких объектов.

Я подумал, что все, что мне нужно было сделать, это переопределить «размещение нового» вместо «обычного» оператора new из-за следующего из документации сайта Boost для make_shared:

"Эффекты: выделяет память, подходящую для объекта типа T и создает в нем объект посредством размещения нового выражения new (pv) T () или новый (pv) T (std :: forward (args) ...) . allocate_shared использует копию для выделения памяти. Если выдается исключение, не имеет эффект. "

Мое место размещения также не вызывается. Я написал небольшую тестовую программу для воспроизведения поведения:

#include <iostream>
using namespace std;
#include "boost/shared_ptr.hpp"
#include "boost/make_shared.hpp"

class Test
{
public:
    Test() { cout << "Test::Test()" << endl; }

    void* operator new (std::size_t size) throw (std::bad_alloc) {
        cout << "Test new" << endl;
        return malloc(size);
    }

    void* operator new (std::size_t size, const std::nothrow_t& nothrow_constant) throw() {
        cout << "Test non-throwing new" << endl;
        return malloc(size);
    }

    void* operator new (std::size_t size, void* ptr) throw() {
        cout << "Test non-throwing placement new" << endl;
        return malloc(size);
    }
};

void* operator new (std::size_t size) throw (std::bad_alloc) {
    cout << "Global new" << endl;
    return malloc(size);
}

int main() {
    cout << "..." << endl;
    boost::shared_ptr<Test> t1(boost::make_shared<Test>());
    cout << "..." << endl;
    boost::shared_ptr<Test> t2(new Test());
    cout << "..." << endl;

    return 0;
}

, который выводит следующий вывод:

...
Global new
Test::Test()
...
Test new
Test::Test()
Global new
...

Я ожидал "Тест нового размещения без бросков" в 3-й строке вывода. Как вы думаете, какое поведение должно быть? Согласны ли вы с тем, что согласно документации make_shared он должен вызывать оператор размещения для моего класса Test? Или я неправильно понял?

Я мог бы скопировать локально усиливающую реализацию и добавить вызов к оператору размещения new. Но будет ли это уместным, или это нарушит предполагаемую семантику размещения новых?

Заранее спасибо за ваше время и вашу помощь.

Ответы [ 3 ]

8 голосов
/ 13 марта 2012

В качестве источника make_shared он использует глобальный оператор размещения new вместо нового оператора, предоставляемого вашим классом.

::new( pv ) T();

К сожалению (как минимум в ОСX) ( в соответствии со стандартом ), вы не можете определить своего нового оператора глобального размещения.Похоже, что allocate_shared больше соответствует тому, что вы ищете.

Редактировать :

Альтернативой может бытьна самом деле напишите версию make_shared, которая использует размещение класса новое вместо глобального.Это всего около 10 строк кода, и должно быть хорошо, если вы соблюдаете лицензию исходного кода .

4 голосов
/ 13 марта 2012

Вы не можете заменить место размещения новым ( §18.4. 1.3 , см., Например, этот вопрос ), поэтому приведенный вывод выглядит нормально.

В качестве альтернативы модификации заголовков Boost вы можете посмотреть на внешние инструменты, такие как Valgrind.

3 голосов
/ 13 марта 2012

Ваш operator new, реализованный для вашего конкретного типа, будет использоваться только в выражениях, для которых элементы вашего типа динамически размещаются с new, например, Test *p = new Test;. Теперь make_shared не динамически распределяет объект вашего типа, а скорее буфер , который содержит достаточно информации для общего подсчета (который включает в себя счетчик, удалитель и несколько лишних кусочков) и ваш объект.

Затем он использует place-new для вызова конструктора вашего объекта. Обратите внимание, что размещение нового в этом случае не выделяет память, это всего лишь забавный синтаксис в C ++ для вызова конструктора в блоке уже выделенной памяти. На самом деле это может привести к путанице, так как выражение new, ваши operator new и Place-New - это три разные концепции, которые имеют общее имя.

...