реализации shared_ptr


Я написал связанного списка версию что-то вроде shared_ptr, который разрушается, когда последняя копия указатель будет уничтожен.

В стороне от потока необеспеченность, это нормально реализовать? Что я могу улучшить?

Кроме того, что это лучший способ, чтобы расширить его, чтобы он работал с рукояткойS, и т. д., без повторения (или ограничить) себя без надобности, в то время как избегая лишней детализации (например, auto_ > считается слишком многословен)?

template<typename T>
class auto_
{
    T *pValue;
    mutable const auto_<T> *pPrev, *pNext;

public:
    auto_()           : pValue(new T()),  pPrev(NULL), pNext(NULL) { }
    auto_(T *pValue)  : pValue(pValue),   pPrev(NULL), pNext(NULL) { }
    auto_(const T &v) : pValue(new T(v)), pPrev(NULL), pNext(NULL) { }
    auto_(const auto_<T> &o) : pValue(o.pValue), pPrev(&o), pNext(NULL) { o.pNext = this; }

    virtual ~auto_()
    {
        const auto_<T> *const pPrev = this->pPrev, *const pNext = this->pNext;
        if (pPrev != NULL) { pPrev->pNext = pNext; }
        if (pNext != NULL) { pNext->pPrev = pPrev; }
        if (pPrev == NULL && pNext == NULL) { delete this->pValue; }
        this->pPrev = this->pNext = NULL;
        this->pValue = NULL;
    }

    auto_<T>& operator=(const auto_<T>& other)
    {
        if (this != &other)
        {
            this->~auto_();
            this->pValue = other.pValue;
            this->pPrev = &other;
            this->pNext = other.pNext;
            if (other.pNext != NULL) { other.pNext->pPrev = this; }
            other.pNext = this;
        }
        return *this;
    }

    operator   T&() /*also const version*/ { return *this->pValue; }
    operator   T*() /*also const version*/ { return  this->pValue; }
    T* operator->() /*also const version*/ { return  this->pValue; }
    T& operator *() /*also const version*/ { return *this->pValue; }
};

Пример использования:

template<typename T>
T recurse(T value, int depth)
{
    if (depth > 0) { T result = recurse(value, depth - 1); return result; }
    else { return value; }
}

auto_<int> test()
{
    printf("Value: %d\n", *recurse(auto_<int>(10), 3));
    auto_<int> p1 = recurse<auto_<int> >(5, 3);
    printf("Value: %d\n", *p1);
    auto_<int> p2 = 3;
    p1 = p2;
    p2 = p1;
    return p2;
}


4662
5
задан 2 сентября 2011 в 07:09 Источник Поделиться
Комментарии
2 ответа

Реализовать свой интеллектуальный указатель-это очень трудно, пожалуйста, не пытайся.

Если это просто, чтобы попробовать и узнать, тогда ладно вы можете узнать что-то, но после практики вернуться к тому, который был испытан и знать, чтобы работать.

20 секунд на просмотр Ошибка 1:

int main()
{
auto_<int> x;
auto_<int> y(x);
auto_<int> z(x);
}

Проблемы, вызванные этой линии:

auto_(const auto_<T> &o)
: pValue(o.pValue)
, pPrev(&o)
, pNext(NULL)
{
o.pNext = this; // Here you are overwriting x.pNext when building z
} // It may or may not cause a bug but it was definitely
// not what you intended to do.

Теперь у вас есть

pNext List
[X] -> [Z] -> | Y/Z point at nothing
[Y] -> | X points at Z

pPrev List
[Y] -> [X] -> | Y/Z point at X
[Z] ----^ X points at nothing.

One assumes you are trying to create a circular list!

Не используйте венгерскую нотацию

pValue

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

Наконечник Стиль

Дон;т сделать это:

    const auto_<T> *const pPrev = this->pPrev, *const pNext = this->pNext;

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

    auto_<T> const* const pPrev = this->pPrev
auto_<T> const* const pNext = this->pNext;

Не делай лишней работы

    this->pPrev = this->pNext = NULL;
this->pValue = NULL;

Это ничего не делает. Этот объект будет уничтожен. Следовательно, эти переменные не существуют после того, как деструктор существует. Так мало смысла играть с их значениями непосредственно перед разрушением.

Не вручную вызвать деструктор

        this->~auto_();

Никто не ожидает, что люди для этого. Его просто сбивает с толку. Переместить код в деструкторе в другой метод и вызвать, что.

Ссылка битая

Если кто-то вставляет нулевой указатель на ваш смарт-указатель, вы напрашиваетесь на неприятности, когда они де-ссылаться на него.

auto_<int>  x(NULL); // valid constructor

Это собирается взорвать

operator   T&() /*also const version*/ { return  *this->pValue; }

Вам необходимо либо проверить PTR на строительство (чтобы удостовериться, что это никогда не является нулем) или если вы позволите нулевые указатели, то вам нужно проверить, если объект используется.

Специализация

Чтобы избежать повторения себя разрешить ваш код, чтобы быть специализированные второй параметр шаблона, который понимает, как вернуть ресурсы разных типов. Версия по умолчанию только входящие удалить, но это позволяет использовать специализировать его на ручках и т. д.

Круговой списки проще не нулевые указатели.

template<typename T>
struct Deleter
{
void operator()(T* ptr) const { delete ptr;}
};

template<typename T, typename D = Deleter<T> >
class my_auto
{
T *value;
mutable const my_auto<T>* prev;
mutable const my_auto<T>* next;

public:
// set up the chain to be circular pointing at just itself.
// This way next/prev will never be NULL and we don't need to test
my_auto() : value(new T()), prev(this), next(this) { }
my_auto(T *value) : value(value), prev(this), next(this) { if (value == 0) throw int(1);}
my_auto(const T &v) : value(new T(v)), prev(this), next(this) { }

// insertInto() will do that appropriate set up.
// We are currently not in a chain and have no value to release.
my_auto(const my_auto<T> &o)
{
insertInto(o);
}

my_auto<T>& operator=(const my_auto<T>& other)
{
// Check for assignment to self
if (this != &other)
{
testAndDestroy();
removeFromChain();
insertInto(other);
}
return *this;
}

~my_auto()
{
testAndDestroy();
removeFromChain();
}

private:
// If next == this then this is the only node in the chain
// So we destroy the data portion.
void testAndDestroy()
{
if (next == this)
{
D deleter;
deleter(value);
}
}
// Unlink this node from the chain.
// If we are the only link in the chain it still works
// we just remain linked to ourselves.
void removeFromChain()
{
// Remove this node from the chain
prev->next = next;
next->prev = prev;
}
// Insert into another chain.
// Assumes that we are not part of another chain
// and that our data has already been released as required.
void insertInto(const my_auto<T>& other)
{
value = other.value;

next = other.next;
other.next.prev = this;

prev = other;
other.next = this;
}
};

Если вы хотите использовать некруглых список (то что нужно исправить)конструктор

auto_(const auto_<T> &o)
: pValue(o.pValue)
, pPrev(&o)
, pNext(o.pNext) // Fix this line
{
if (o.pNext) { o.pNext.prev = this;} // Add this line
o.pNext = this;
}
// Your code assumes the node is always added to the end of the list
// You can not guarantee this. So you need to take account of being
// inserted into the middle.

11
ответ дан 2 сентября 2011 в 09:09 Источник Поделиться

Вы размещаете ссылку на результаты бенчмарков, которые очень интересные. Они показывают свой указатель класса значительно быстрее, чем прирост shared_ptr класс.

Здесь есть несколько рецензий. Ваш метод бенчмаркинга-это немного сырой, и это очень сильно ориентированы на изготовление копий указателя. Хотя большинство указателей копируются немного больше, чем они созданы, отношение больше как 5 к 1, а не 16 миллионов к 1.

Но, конечно, это делает ваш тест все более интересные с shared_ptrs большая слабость по сравнению с ваше требование о распределении памяти, когда указатель создается впервые. Конечно, через make_shared в избавляется от нагрузки и, как бонус, увеличивает локальность ссылок с подсчетом ссылок и данных, указывающих на находятся недалеко друг от друга в памяти.

Я запустил этот тест на Linux Оптерон система, основанная с Boost 1.47 и получили почти идентичные результаты с вашими.

Потом я немного преобразовали программа. В C++11 имеет shared_ptr в качестве части стандартной библиотеки. Я изменил свою программу, чтобы использовать вместо этого и составлен для C++11.

После этого производительность вашего класса был только немного лучше, чем стандартная библиотека классов. 960ms и 800ms.

Я потом пересобрал с -pthread в возможность и результативность стандартной библиотеки shared_ptr резко сократились. 1660ms 820ms против.

Это подтверждает мою теорию, что плохой прирост производительности, скорее всего, из-за потокобезопасность. Изменение счетчика ссылок в потокобезопасным способом значительно медленнее, потому что процессор вынужден использовать атомарные операции с памятью.

В любом случае, разница между вашим классом и стандартные библиотеки классов настолько мало в непоточном контексте, что я не чувствую, это стоило того, чтобы использовать свой специальный класс против одного, что является частью стандартной библиотеки.

Если вы хотите заново создать тест, я побежал, изменение этих строк в тесте:

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

для

#include <memory>
using ::std::shared_ptr;
using ::std::make_shared;

И скомпилировать с компилятором, который поддерживает C++11. г++ может быть сделано для поддержки C++11 при прохождении -с std=с GNU++0х или -с std=с++0х вариантов. По умолчанию компилятор GNU будет компилироваться в однопоточный режим. Для того, чтобы получить его для компиляции программы, которые будут работать с несколькими потоками, с -pthread в вариант должен быть принят как компилятор и компоновщик.

0
ответ дан 15 декабря 2011 в 06:12 Источник Поделиться