Могут ли интеллектуальные указатели выборочно скрывать или перенаправлять вызовы функций на объекты, которые они оборачивают?

Я работаю над проектом, в котором подсчитываются ссылки на определенные объекты - это очень похоже на настройку COM. В любом случае, в нашем проекте есть интеллектуальные указатели, которые избавляют от необходимости явно вызывать Add() и Release() для этих объектов. Проблема в том, что иногда разработчики все еще вызывают Release() с умным указателем.

То, что я ищу, - это способ вызвать Release() из интеллектуального указателя, чтобы создать ошибку времени компиляции или времени выполнения. Время компиляции мне кажется невозможным. Я думал, что у меня есть решение для выполнения (см. код ниже), но оно также не совсем компилируется. По-видимому, неявное преобразование не допускается после использования operator->().

В любом случае, может ли кто-нибудь придумать способ выполнить то, что я пытаюсь выполнить?

Большое спасибо за вашу помощь!

Кевин

#include <iostream>
#include <cassert>

using namespace std;

class A
{
public:
    void Add()
    {
        cout << "A::Add" << endl;
    }

    void Release()
    {
        cout << "A::Release" << endl;
    }

    void Foo()
    {
        cout << "A::Foo" << endl;
    }
};

template <class T>
class MySmartPtrHelper
{
    T* m_t;

public:

    MySmartPtrHelper(T* _t)
        : m_t(_t)
    {
        m_t->Add(); 
    }

    ~MySmartPtrHelper()
    {
        m_t->Release(); 
    }

    operator T&()
    {
        return *m_t;
    }

    void Add()
    {
        cout << "MySmartPtrHelper::Add()" << endl;
        assert(false);
    }

    void Release()
    {
        cout << "MySmartPtrHelper::Release()" << endl;
        assert(false);
    }
};

template <class T>
class MySmartPtr
{
    MySmartPtrHelper<T> m_helper;

public:

    MySmartPtr(T* _pT)
        : m_helper(_pT)
    {
    }

    MySmartPtrHelper<T>* operator->()
    {
        return &m_helper;
    }
};

int main()
{
    A a;

    MySmartPtr<A> pA(&a);

    pA->Foo(); // this currently fails to compile.  The compiler
               // complains that MySmartPtrHelper::Foo() doesn't exist.

    //pA->Release(); // this will correctly assert if uncommented.

    return 0;
}

person Kevin    schedule 05.06.2009    source источник
comment
Не могли бы вы заменить выделение стека A a новым распределением оператора? Это не изменит исходную проблему, но устранит одну из типичных неприятных ошибок, связанных с использованием интеллектуальных указателей с подсчетом ссылок.   -  person sharptooth    schedule 05.06.2009


Ответы (4)


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

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

person sharptooth    schedule 05.06.2009
comment
Это моя долгосрочная цель. Однако прямо сейчас у нас есть много устаревшего кода, созданного до появления интеллектуальных указателей. К сожалению, у нас приближается крайний срок, и я не могу реализовать это решение до определенного момента в будущем — надеюсь, не в неопределенном будущем. - person Kevin; 05.06.2009

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

Я не могу придумать ни одного подхода, который не включает в себя каким-то образом репликацию интерфейсов ваших объектов, на которые вы указываете, или требует, чтобы вы создавали объекты, публично производные от ваших объектов, на которые указывали, с скрытыми и закрытыми Add и Release в производном классе и используя трюк Base* pBase = pDerived; pBase->Add(); для вызова добавления и выпуска из интеллектуального указателя.

person CB Bailey    schedule 05.06.2009

я заставил его работать, изменив перегруженный оператор в MySmartPtr и добавив оператор перегрузки в MySmartPtrHelper:

#include <iostream>
#include <cassert>

using namespace std;

class A
{
public:
    void Add()
    {
        cout << "A::Add" << endl;
    }

    void Release()
    {
        cout << "A::Release" << endl;
    }

    void Foo()
    {
        cout << "A::Foo" << endl;
    }
};

template <class T>
class MySmartPtrHelper
{
    T* m_t;

public:

    MySmartPtrHelper(T* _t)
        : m_t(_t)
    {
        m_t->Add(); 
    }

    ~MySmartPtrHelper()
    {
        m_t->Release(); 
    }

    operator T&()
    {
        return *m_t;
    }

    T* operator->()
    {
        return m_t;
    }


    void Add()
    {
        cout << "MySmartPtrHelper::Add()" << endl;
        assert(false);
    }

    void Release()
    {
        cout << "MySmartPtrHelper::Release()" << endl;
        assert(false);
    }
};

template <class T>
class MySmartPtr
{
    MySmartPtrHelper<T> m_helper;

public:

    MySmartPtr(T* _pT)
        : m_helper(_pT)
    {
    }

    T* operator->()
    {
        return m_helper.operator->();
    }
};

int main()
{
    A a;

    MySmartPtr<A> pA(&a);

    pA->Foo(); 
    //pA->Release(); // this will correctly assert if uncommented.

    return 0;
}

Выход:

macbook-2:~ $ ./a.out 
A::Add
A::Foo
A::Release
person Lodle    schedule 05.06.2009
comment
Какой смысл привязывать указатель подсчета ссылок к объекту, размещенному в стеке? - person sharptooth; 05.06.2009
comment
я не знаю, это был код, который он разместил выше, но исправлен, так что он работает. - person Lodle; 05.06.2009
comment
Нет, это не работает, как я надеялся. Если pA->Release() не закомментирован, утверждения нет. Идея заключалась в том, чтобы перенаправить вызов на Release(). - person Kevin; 05.06.2009

Я предлагаю вам использовать что-то вроде следующего кода. То, что вы хотите, невозможно, если вы не хотите добавить небольшое ограничение: объекты должны быть копируемыми (и вы не возражаете против использования этой возможности). В этом случае наследование является хорошим способом.

#include <iostream>
#include <cassert>

using namespace std;

template <class T>
class MySmartPtrHelper : public T
{

public:

    MySmartPtrHelper(T* _t)
        : m_t(*_t)
    {
        delete _t;
        ((T*) this)->Add();
    }

    ~MySmartPtrHelper()
    {
        ((T*) this)->Release(); 
    }

    void Add()
    {
        cout << "MySmartPtrHelper::Add()" << endl;
        //will yield a compile-time error  
        BOOST_STATIC_ASSERT(false) 
    }

    void Release()
    {
        cout << "MySmartPtrHelper::Release()" << endl;
        //will yield a compile-time error  
        BOOST_STATIC_ASSERT(false) 
    }
};

template <class T>
class MySmartPtr
{
   MySmartPtrHelper<T>* m_helper;
   // Uncomment if you want to use boost to manage memory
   // boost::shared_ptr<MySmartPtrHelper<T> > m_helper;

public:

    MySmartPtr(T* _pT)
        : m_helper(new MySmartPtrHelper<T>(_pT))
    {
    }

    MySmartPtrHelper<T>* operator->()
    {
        return m_helper;
    }
};

int main()
{
    MySmartPtr<A> pA(new A());

    pA->Foo();

    //pA->Release(); // this will correctly assert if uncommented.

    return 0;
}
person Benoît    schedule 05.06.2009
comment
Когда вы копируете интеллектуальный указатель, указанный объект также будет скопирован, что полностью противоречит цели наличия интеллектуального указателя подсчета ссылок. - person Luc Touraille; 05.06.2009
comment
Вы правы, но мое решение остается в силе, пока BOOST_STATIC_ASSERT используется для генерации ошибок времени компиляции. Цель состоит не в том, чтобы сгенерировать действительно пригодный для использования код, но его можно использовать для удаления бесполезных вызовов Add() и Release(). - person Benoît; 06.06.2009