О смарт-указателях
От: rus blood Россия  
Дата: 28.10.04 11:25
Оценка: :)
Господа, есть такая ситуация.

Есть вот такой примитив.


#include <iostream>
using namespace std;

template <class T>
class _pointer
{
public:
    _pointer() : m_t(0) {}
    _pointer(T* t) : m_t(t) {}
    ~_pointer() { if (m_t) delete m_t; }

    operator T* () { return m_t; }

private:
    explicit _pointer(const _pointer&) {}
    const _pointer& operator =(const _pointer&) { return *this; }

private:
    T* m_t;
};

class SomeClass
{
public:
    SomeClass() { cout << "ctor" << endl; }
    ~SomeClass() { cout << "dtor" << endl; } 
};

typedef _pointer<SomeClass> SomeClassPtr;

SomeClass* MakeSomeClass()
{ return new SomeClass; }

int main(int argc, char* argv[])
{
    SomeClassPtr pPtr = MakeSomeClass();
    cout << "test" << endl;
    return 0;
}


Вопрос: что будет выведено на экран.

Вот что думает по этому поводу VC71:

ctor
test
dtor


А вот что думает по этому поводу g++ (хз какой версии), из KDevelop2.1 в поставке Linux SuSE8:

ctor
dtor
test
dtor

и segmentation fault, как результат.

Точнее, происходит следующее
1. Вызывается конструктор _pointer(T*);
2. Вызывается operator T*();
3. Вызывается конструктор _pointer(T*);
4. Вызывается деструктор ~_pointer;
Т.е. создается какой-то временный объект, который копируется в pPtr.
Более того, если убрать explicit у конструктора копирования, получаем недвусмысленное сообщение компилятора, что он хочет именно конструктор копирования...


Так что же с этим делать? Кто прав? Кого бить???

Спасибо.
Имею скафандр — готов путешествовать!
Re: О смарт-указателях
От: korzhik Россия  
Дата: 28.10.04 11:35
Оценка: 4 (1)
Здравствуйте, rus blood, Вы писали:

RB>Господа, есть такая ситуация.


RB>Есть вот такой примитив.


RB>

RB>#include <iostream>
RB>using namespace std;

RB>template <class T>
RB>class _pointer
RB>{
RB>public:
RB>    _pointer() : m_t(0) {}
RB>    explicit _pointer(T* t) : m_t(t) {} // а если так
RB>    ~_pointer() { if (m_t) delete m_t; }

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

RB>private:
RB>    explicit _pointer(const _pointer&) {}
RB>    const _pointer& operator =(const _pointer&) { return *this; }

RB>private:
RB>    T* m_t;
RB>};

RB>class SomeClass
RB>{
RB>public:
RB>    SomeClass() { cout << "ctor" << endl; }
RB>    ~SomeClass() { cout << "dtor" << endl; } 
RB>};

RB>typedef _pointer<SomeClass> SomeClassPtr;

RB>SomeClass* MakeSomeClass()
RB>{ return new SomeClass; }

RB>int main(int argc, char* argv[])
RB>{
RB>    SomeClassPtr pPtr = MakeSomeClass();
RB>    cout << "test" << endl;
RB>    return 0;
RB>}
RB>


RB>Вопрос: что будет выведено на экран.


RB>Вот что думает по этому поводу VC71:

RB>

RB>ctor
RB>test
RB>dtor


RB>А вот что думает по этому поводу g++ (хз какой версии), из KDevelop2.1 в поставке Linux SuSE8:

RB>

RB>ctor
RB>dtor
RB>test
RB>dtor

RB>и segmentation fault, как результат.

RB>Точнее, происходит следующее

RB>1. Вызывается конструктор _pointer(T*);
RB>2. Вызывается operator T*();
RB>3. Вызывается конструктор _pointer(T*);
RB>4. Вызывается деструктор ~_pointer;
RB>Т.е. создается какой-то временный объект, который копируется в pPtr.
RB>Более того, если убрать explicit у конструктора копирования, получаем недвусмысленное сообщение компилятора, что он хочет именно конструктор копирования...


RB>Так что же с этим делать? Кто прав? Кого бить???


RB>Спасибо.
Re: О смарт-указателях
От: Анатолий Широков СССР  
Дата: 28.10.04 11:40
Оценка:
Здравствуйте, rus blood, Вы писали:

RB>Господа, есть такая ситуация.


RB>Есть вот такой примитив.


Вообще, ужасный примитив. Переписать не хотите?
Re: О смарт-указателях
От: Кодт Россия  
Дата: 28.10.04 11:41
Оценка: 4 (1)
Здравствуйте, rus blood, Вы писали:

RB>int main(int argc, char* argv[])
RB>{
RB>    SomeClassPtr pPtr = MakeSomeClass();
RB>    cout << "test" << endl;
RB>    return 0;
RB>}


Инициализация копированием. В этом случае компилятор может пойти на оптимизацию (RVO), а может не пойти.

То, что ты сделал конструктор копирования приватным — не спасает, поскольку "для своих" он всё равно в зоне видимости.
Вот если бы ты его ещё и не определял (а только объявил)... Тогда тебе надавали бы по пальцам.

Ну и про explicit конструктор тоже сказано.
Перекуём баги на фичи!
Re[2]: О смарт-указателях
От: rus blood Россия  
Дата: 28.10.04 11:43
Оценка:
Здравствуйте, korzhik, Вы писали:

Это дельное замечание, но делу оно не помогает.
Conversion from 'SomeClass *' to non-scalar type '_pointer' requested.
Что собственно и следовало ожидать...

Короче,

вот так работает НЕправильно
_pointer<SomeClass> pPtr = new SomeClass;

а вот так работает правильно
_pointer<SomeClass> pPtr(new SomeClass);


Почему???
Имею скафандр — готов путешествовать!
Re[2]: О смарт-указателях
От: korzhik Россия  
Дата: 28.10.04 11:45
Оценка:
Здравствуйте, korzhik, Вы писали:

RB>>
RB>>template <class T>
RB>>class _pointer
RB>>{
RB>>public:
RB>>    _pointer() : m_t(0) {}
RB>>    explicit _pointer(T* t) : m_t(t) {} // а если так
RB>>    ~_pointer() { if (m_t) delete m_t; } // здесь можно просто delete m_t
RB>>};
RB>>

прошу прошения за оверквотинг в предыдущем сообшении, поспешил, рано нажал на кнопку.
Re[2]: О смарт-указателях
От: rus blood Россия  
Дата: 28.10.04 11:46
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Инициализация копированием. В этом случае компилятор может пойти на оптимизацию (RVO), а может не пойти.

Это отчего зависит?

К>То, что ты сделал конструктор копирования приватным — не спасает, поскольку "для своих" он всё равно в зоне видимости.

К>Вот если бы ты его ещё и не определял (а только объявил)... Тогда тебе надавали бы по пальцам.

Приватность как раз помогает. При наличии explicit компилятор обходит приватность путем вызова operator T*().
Вот если убрать operator T*(), или поставить explicit у конструктора _pointer(T*), то все...
Имею скафандр — готов путешествовать!
Re[2]: О смарт-указателях
От: rus blood Россия  
Дата: 28.10.04 11:47
Оценка:
Здравствуйте, Анатолий Широков, Вы писали:

АШ>Вообще, ужасный примитив. Переписать не хотите?

Нет, не хочу. Как Вы наверно догадываетесь, примитив составлен для форума и предъявления проблемы...
Имею скафандр — готов путешествовать!
Re: Суть вопроса.
От: rus blood Россия  
Дата: 28.10.04 11:52
Оценка:
Здравствуйте, rus blood, Вы писали:

Почему запись
_pointer<SomeClass> p = new SomeClass;

воспринимается как
_pointer<SomeClass> p = _pointer<SomeClass>(new SomeClass);

а не как
_pointer<SomeClass> p(new SomeClass);


И можно ли это изменить?
Имею скафандр — готов путешествовать!
Re[2]: Суть вопроса.
От: _nn_ www.nemerleweb.com
Дата: 28.10.04 12:14
Оценка:
Здравствуйте, rus blood, Вы писали:

RB>Здравствуйте, rus blood, Вы писали:


RB>Почему запись

RB>
RB>_pointer<SomeClass> p = new SomeClass;
RB>

RB>воспринимается как
RB>
RB>_pointer<SomeClass> p = _pointer<SomeClass>(new SomeClass);
RB>

RB>а не как
RB>
RB>_pointer<SomeClass> p(new SomeClass);
RB>


RB>И можно ли это изменить?


Если записать немного по другому думаю станет ясно:
class A
{
public:
 A() {}
 A(int) {}
};

Оинаковые строки:
A a = A();
A a;

A a = A(1);
A a(1);

это сделанно для инициализации массивов:
A a[3]={A(),A(1),A(2)};
// как-будто :
// a[0] = A() ==> a[0])()
// a[1] = A(1) ==> a[1](1)
// a[2] = A(2) ==> a[2](2)


Запретить преобразование нельзя, потому что так работает конструкция инициализации.
Но можно использовать слово exlicit и тогда нельзя будет писать неявный вызов конструктора.
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[3]: О смарт-указателях
От: Кодт Россия  
Дата: 28.10.04 12:14
Оценка: 8 (1)
Здравствуйте, rus blood, Вы писали:

К>>Инициализация копированием. В этом случае компилятор может пойти на оптимизацию (RVO), а может не пойти.

RB>Это отчего зависит?

От способностей и опций компилятора.
В Стандарте где-то прописано, что в ряде случаев компилятор имеет право не создавать промежуточную копию — при инициализации и при возвращении (собственно, RVO расшифровывается как return value optimization).

object foo()
{
  ...
  return object(x,y,z); // здесь можно напрямую сконструировать результат
  ...
  object v;
  ...
  return v; // здесь придётся копировать
  ...
  return p() ? v : object(x,y,z); // а здесь однозначно придётся копировать - или из v, или из временного объекта
  ...
}

// аналогично - при инициализации

object a(x,y,z); // конструктор в явном виде

object b = object(x,y,z);
object c = b;
object d = p() ? a : object(x,y,z);
Перекуём баги на фичи!
Re[2]: Суть вопроса.
От: 0xFADE США github.com/NotImplemented
Дата: 28.10.04 12:17
Оценка:
Здравствуйте, rus blood, Вы писали:

RB>Здравствуйте, rus blood, Вы писали:


RB>Почему запись

RB>
RB>_pointer<SomeClass> p = new SomeClass;
RB>

RB>воспринимается как
RB>
RB>_pointer<SomeClass> p = _pointer<SomeClass>(new SomeClass);
RB>

RB>а не как
RB>
RB>_pointer<SomeClass> p(new SomeClass);
RB>


RB>И можно ли это изменить?


Нет.
8.5/14
Re[3]: Суть вопроса.
От: rus blood Россия  
Дата: 28.10.04 12:37
Оценка:
Здравствуйте, _nn_, Вы писали:

__>Если записать немного по другому думаю станет ясно:

__>
...
__>


Честно говоря, яснее не стало. Скорее, аналогией было бы
A a = 1;
// Как воспримет компилятор?
// Так
A a = A(1);
// или так
A a(1);

Но все равно, спасибо.

__>Запретить преобразование нельзя, потому что так работает конструкция инициализации.

__>Но можно использовать слово exlicit и тогда нельзя будет писать неявный вызов конструктора.
Но ведь семерка не преобразует!
Имею скафандр — готов путешествовать!
Re[3]: Суть вопроса.
От: rus blood Россия  
Дата: 28.10.04 12:37
Оценка:
Здравствуйте, 0xFADE, Вы писали:

RB>>И можно ли это изменить?


FAD>Нет.

Так категорично?
FAD>8.5/14
Можно текст?
Имею скафандр — готов путешествовать!
Re[4]: Суть вопроса.
От: korzhik Россия  
Дата: 28.10.04 12:48
Оценка: 4 (1)
Здравствуйте, rus blood, Вы писали:

__>>Запретить преобразование нельзя, потому что так работает конструкция инициализации.

__>>Но можно использовать слово exlicit и тогда нельзя будет писать неявный вызов конструктора.
RB>Но ведь семерка не преобразует!

семёрка как раз использует этот конструктор:
_pointer(T* t) : m_t(t) {}


Кодт уже объяснил, что в данном случае при инициализации может быть создан временный объект, а может быть и не создан.
стандарт говорит что допускается такая оптимизация, но будет ли её делать компилятор не известно.
Вывод: использование в вашем случае не explicit конструктора просто не допустимо.
Re[4]: Суть вопроса.
От: 0xFADE США github.com/NotImplemented
Дата: 28.10.04 13:11
Оценка: 8 (1)
Здравствуйте, rus blood, Вы писали:

RB>Здравствуйте, 0xFADE, Вы писали:


RB>>>И можно ли это изменить?


FAD>>Нет.

RB>Так категорично?
FAD>>8.5/14
RB>Можно текст?

Пожалуйста.

The semantics of initializers are as follows. The destination type is the type of the object or reference and the source type is the type of the initializer expression.
...
— If the destination type is a (possibly cv-qualified) class type:
— If the class is an aggregate ... // не подходит
— If the initialization is a direct-initialization, or if it is copy-initialization of the source type is the same class as, or derived class of, the class of the destination — // не подходит, так как в вашем случае source type является SomeClass*, а destination type есть _pointer<SomeClass>
— Otherwise (i. e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in 13.3.1.4, and the best one is chosen through overload resolution (13.3)...
The function selected is called with the initializer expression as its argument; if the function is a constructor, the call initializes a temporary of the destination type. The result of the call (which is the temporary for constructor case) is then used to direct-initialize, according to rules above, the object that is the destination of the copy-initialization. И далее, об оптимизации: In certain cases, an implementation is permitted to eliminate the copying inherent in this direct initialization by constructing the intermediate result directly into the object being initialized; see 12.2, 12.8.
Re: О смарт-указателях
От: 0xDEADBEEF Ниоткуда  
Дата: 28.10.04 13:13
Оценка:
Hello, rus blood!
You wrote on Thu, 28 Oct 2004 11:25:54 GMT:

rb> Вот что думает по этому поводу VC71:

rb>

rb> ctor
rb> test
rb> dtor

...VC есть вумный (причем часто не в меру) и поэтому имплементит copy-initialization сразу оптимизированной.

rb> А вот что думает по этому поводу g++ (хз какой версии), из KDevelop2.1

rb> в поставке Linux SuSE8:

rb> ctor
rb> dtor (вот здесь m_t удаляется первый раз)
rb> test
rb> dtor (...a вот здесь второй... и ты за это будешь наказан)

rb> и segmentation fault, как результат.
...а вот GCC не есть вумный, и поэтому имплементит copy-initialization как в книжке (то есть стандарте) написано и плевать он хотел на то что это дело соптимизить можно...

rb> Так что же с этим делать?

Править. А именно, добавить такой вот copy-constructor в _pointer

_pointer(pointer const& rhs): m_t(rhs.m_t)
{
const_cast<pointer&>(rhs).m_t = 0;//чтобы избежать двойного удаления
}

rb> Кто прав?

Оба правы.


rb> Кого бить???

В зеркале увидишь .
Posted via RSDN NNTP Server 1.9 gamma
__________
16.There is no cause so right that one cannot find a fool following it.
Re[2]: О смарт-указателях
От: korzhik Россия  
Дата: 28.10.04 13:21
Оценка:
Здравствуйте, 0xDEADBEEF, Вы писали:

rb>> Так что же с этим делать?

DEA>Править. А именно, добавить такой вот copy-constructor в _pointer

DEA>_pointer(pointer const& rhs): m_t(rhs.m_t)

DEA>{
DEA> const_cast<pointer&>(rhs).m_t = 0;//чтобы избежать двойного удаления
DEA>}

ну уж если и идти таким путём, то можно и без const_cast:
_pointer(pointer& rhs): m_t(rhs.m_t)
{
    rhs.m_t = 0;//чтобы избежать двойного удаления
}
Re[3]: О смарт-указателях
От: Кодт Россия  
Дата: 28.10.04 13:32
Оценка:
Здравствуйте, korzhik, Вы писали:

DEA>>Править. А именно, добавить такой вот copy-constructor в _pointer

DEA>>_pointer(pointer const& rhs): m_t(rhs.m_t)
DEA>>{
DEA>>    const_cast<pointer&>(rhs).m_t = 0;//чтобы избежать двойного удаления
DEA>>}

И получить ещё одну кривую реализацию std::auto_ptr

K>ну уж если и идти таким путём, то можно и без const_cast:

K>
K>_pointer(pointer& rhs): m_t(rhs.m_t)
K>{
K>    rhs.m_t = 0;//чтобы избежать двойного удаления
K>}
K>

А это уже багофича VC, которая в ряде случаев конвертирует rvalue в неконстантный lvalue.
Перекуём баги на фичи!
Re[4]: О смарт-указателях
От: 0xDEADBEEF Ниоткуда  
Дата: 28.10.04 13:40
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Здравствуйте, korzhik, Вы писали:


DEA>>>Править. А именно, добавить такой вот copy-constructor в _pointer

К>
DEA>>>_pointer(pointer const& rhs): m_t(rhs.m_t)
DEA>>>{
DEA>>>    const_cast<pointer&>(rhs).m_t = 0;//чтобы избежать двойного удаления
DEA>>>}
К>

К>И получить ещё одну кривую реализацию std::auto_ptr
Какой вопрос, такой и ответ Или ты считаешь что из того примера может получться что-то еще Ж-) ?

... А вообще-то в идее с copy-constructor'om я перемудрил.
Достаточно убрать "explicit" из заприваченного copy-constructor'а и все будет пучком.
То есть, VC будет компилировать, а вот GCC увдиев приватный copy-constructor пошлет этот код куда подальше и правильно сделает.
__________
16.There is no cause so right that one cannot find a fool following it.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.