boost::make_shared не вызывает (размещает) оператора new?

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

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

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

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

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

#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 он должен вызывать новый оператор размещения моего тестового класса? Или я неправильно понял?

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

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


person Joost Sannen    schedule 12.03.2012    source источник
comment
Глядя на ‹make_shared.hpp› boost, он использует оператор new глобального размещения ::new( pv ) T(). Вот почему ваше размещение на уровне класса не вызывается... Удалив глобальный квалификатор '::' перед new, make_shared фактически вызовет новый оператор размещения на уровне вашего класса.   -  person Gob00st    schedule 16.10.2012


Ответы (3)


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

::new( pv ) T();

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

Изменить:

В качестве альтернативы можно было бы написать версию make_shared, которая использует новое размещение класса вместо глобального. Это всего около 10 строк кода, и его должно хватить, если вы соблюдаете лицензию исходного кода< /а>.

person leedm777    schedule 12.03.2012
comment
Стоит отметить, что allocate_shared() может означать замену нескольких вызовов в большей кодовой базе. - person Georg Fritzsche; 13.03.2012
comment
Спасибо за полезный ответ. Я не знал, что не должен переопределять новое размещение. Так что это определенно демонстрация моей попытки решения. Я уже исследовал параметр allocate_shared, но, как сказал Георг, он слишком сильно влияет на существующий код. - person Joost Sannen; 16.03.2012

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

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

person Georg Fritzsche    schedule 12.03.2012

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

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

person David Rodríguez - dribeas    schedule 12.03.2012
comment
Несмотря на это, по-прежнему можно обеспечить перегрузку для каждого класса даже для нового размещения. Возможно, необычно, но опять же, какая часть C++ не является необычной :-) Главное, что нужно усвоить, это то, что std::allocator не использует никаких функций выделения для каждого класса, а только глобальную ::new . - person Kerrek SB; 13.03.2012