Explicit initialization
От: Were  
Дата: 17.06.08 13:37
Оценка:
Есть некий класс:

class ExplicitTest
{
    int m_a;
public:
    explicit ExplicitTest( int a ) :
        m_a( a ) {}
    ExplicitTest( const ExplicitTest &obj ) :
        m_a( obj.m_a ) {}
    virtual ~ExplicitTest() {}
};


Как сделать, чтобы явной считалась и инициализация с помощью знака равно(=)?
Примерно так:
ExplicitTest TestObj1(0);                      // OK
ExplicitTest TestObj1 = ExplicitTest(0);       // OK
ExplicitTest *pTestObj = new ExplicitTest(0);  // OK
ExplicitTest TestObj2 = 0;                     // Want to be OK

void Function( const ExplicitTest &obj);

Function( 0 ); // Error


Я конечно понимаю, что там создается временный объект, но компилятор ведь все равно это оптимизирует ) Вообщем хочется запретить создавать неявно временные объекты и разрешить все остальное.
Re: Explicit initialization
От: Bell Россия  
Дата: 17.06.08 15:16
Оценка:
Здравствуйте, Were, Вы писали:

W>Я конечно понимаю, что там создается временный объект, но компилятор ведь все равно это оптимизирует )

Да, имеет право, но класс все равно должен быть copy-constructable

12.2/1
...
even if the copy constructor is not called, all the semantic restrictions, such as accessibility (clause 11), shall be satisfied.
...


W>Вообщем хочется запретить создавать неявно временные объекты и разрешить все остальное.

Насчет инициализации копированием, т.е. записи вида
ExplicitTest TestObj2 = 0;

ничего не выйдет, если конструктор ExplicitTest::ExplicitTest(int) объявлен как explicit.
Что еще из "всего остального" тебе мешает?
Любите книгу — источник знаний (с) М.Горький
Re[2]: Explicit initialization
От: Were  
Дата: 17.06.08 15:27
Оценка:
Здравствуйте, Bell, Вы писали:

B>ничего не выйдет, если конструктор ExplicitTest::ExplicitTest(int) объявлен как explicit.

Окей, пойдем с другой стороны. Как без объявления explicit конструктора запретить такую форму создания временного объекта?
class ExplicitTest
{
public:
    ExplicitTest( int a );
// ...
};

void Function( const ExplicitTest &obj);

Function( 0 ); // Want to be error


B>Что еще из "всего остального" тебе мешает?

Не понял вопроса?
Re[3]: Explicit initialization
От: Bell Россия  
Дата: 17.06.08 15:34
Оценка:
Здравствуйте, Were, Вы писали:

B>>ничего не выйдет, если конструктор ExplicitTest::ExplicitTest(int) объявлен как explicit.

W>Окей, пойдем с другой стороны. Как без объявления explicit конструктора запретить такую форму создания временного объекта?
W>
W>class ExplicitTest
W>{
W>public:
W>    ExplicitTest( int a );
W>// ...
W>};

W>void Function( const ExplicitTest &obj);

W>Function( 0 ); // Want to be error
W>


Я приличного решения не вижу

B>>Что еще из "всего остального" тебе мешает?

W>Не понял вопроса?

Вообщем хочется запретить создавать неявно временные объекты и разрешить все остальное.

Один пункт ты указал — желаема запись вида ExplicitTest TestObj2 = 0;. Что еще не устраивает в варианте с explicit-конструктором.
Любите книгу — источник знаний (с) М.Горький
Re[4]: Explicit initialization
От: Were  
Дата: 17.06.08 15:59
Оценка:
Здравствуйте, Bell, Вы писали:

B>Я приличного решения не вижу


Жаль (

B>Один пункт ты указал — желаема запись вида ExplicitTest TestObj2 = 0;. Что еще не устраивает в варианте с explicit-конструктором.


А, в остальном все устраивает )
Re: Explicit initialization
От: Кодт Россия  
Дата: 17.06.08 16:12
Оценка: 3 (1)
Здравствуйте, Were, Вы писали:

W>Как сделать, чтобы явной считалась и инициализация с помощью знака равно(=)?

W>Примерно так:
W>
W>ExplicitTest TestObj1(0);                      // OK
W>ExplicitTest TestObj1 = ExplicitTest(0);       // OK
W>ExplicitTest *pTestObj = new ExplicitTest(0);  // OK
W>ExplicitTest TestObj2 = 0;                     // Want to be OK

W>void Function( const ExplicitTest &obj);

W>Function( 0 ); // Error
W>


Ну, скажем, так
class Param // то, что мы принимаем и используем
{
protected:
    // конструкторы
    Param();
    explicit Param(int); // здесь explicit для того, чтоб не ошибиться, пребывая в дружественном контексте
    .....
public:
    // всё остальное, предназначенное для использования
    .....
};

class Value : public Param // то, что мы создаём
{
public:
    // только конструкторы
    Value() {}
    /*implicit*/ Value(int x) : Param(x) {}
    .....
};

void Function(const Param& obj);

void usage()
{
    Value v1(0);
    Value v2 = 0;
    Function(Value(0));
    Function(0); // error
}

Эту идею можно сделать более удобной — скажем, превратив Value в шаблон
template<class Param>
class Value : public Param
{
public:
    Value() {}

    template<class A1>
    /*implicit*/ Value(A1 const& a1) : Param(a1) {}
    
    template<class A1, class A2>
    Value(A1 const& a1, A2 const& a2) : Param(a1,a2) {}
    
    // и т.д.
};
... << RSDN@Home 1.2.0 alpha rev. 655>>
Перекуём баги на фичи!
Re[3]: Explicit initialization
От: Vain Россия google.ru
Дата: 17.06.08 18:59
Оценка:
Здравствуйте, Were, Вы писали:

B>>ничего не выйдет, если конструктор ExplicitTest::ExplicitTest(int) объявлен как explicit.

W>Окей, пойдем с другой стороны. Как без объявления explicit конструктора запретить такую форму создания временного объекта?
W>
W>class ExplicitTest
W>{
W>public:
W>    ExplicitTest( int a );
W>// ...
W>};

W>void Function( const ExplicitTest &obj);

W>Function( 0 ); // Want to be error
W>

Здесь
Автор: Vain
Дата: 09.06.08

Здесь
Автор: Vain
Дата: 13.01.08
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Re[2]: Explicit initialization
От: Were  
Дата: 17.06.08 20:59
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Ну, скажем, так


В качестве "защиты от дурака", ради чего и задумывалась эта тема, данный подход не подойдет, так как ничто не мешает написать:
void Function(const Value& obj);

Кроме того особенности использования класса, в котором хочется это применить не позволяют плодить наследников только ради этой самой защиты )
Re[4]: Explicit initialization
От: Were  
Дата: 17.06.08 21:09
Оценка:
Здравствуйте, Vain, Вы писали:

V>Здесь
Автор: Vain
Дата: 09.06.08

V>Здесь
Автор: Vain
Дата: 13.01.08


Как я уже написал выше, это "защита от дурака", так что врядли "дурак" захочет объявлять дополнительные прототипы своей функции )) По большому счету он об этом вообще не знает. А заботиться о том, как пытаются создать объект, должен сам класс.
Re[5]: Explicit initialization
От: Vain Россия google.ru
Дата: 17.06.08 22:05
Оценка:
Здравствуйте, Were, Вы писали:

V>>Здесь
Автор: Vain
Дата: 09.06.08

V>>Здесь
Автор: Vain
Дата: 13.01.08

W>Как я уже написал выше, это "защита от дурака", так что врядли "дурак" захочет объявлять дополнительные прототипы своей функции )) По большому счету он об этом вообще не знает. А заботиться о том, как пытаются создать объект, должен сам класс.
Если "дурак" пишет на C++, ему никакие классы не помогут.
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Re[3]: Explicit initialization
От: Кодт Россия  
Дата: 18.06.08 08:45
Оценка: 5 (1)
Здравствуйте, Were, Вы писали:

W>В качестве "защиты от дурака", ради чего и задумывалась эта тема, данный подход не подойдет, так как ничто не мешает написать:


Инициативный дурак что хошь сломает.
Но ты определи — какую сторону защищать собрался.




Если пишешь Function и хочешь избежать вызова её с неверными параметрами — это одно. Кстати, для небольшого наперёд заданного набора функций достаточно сделать ловушки
void Function(Value const&);
template<class T> void Function(T const& x) { Function(up_cast<Value>(x)); }


template<class To, class From>
typename enable_if<is_base_and_derived<To,From>, To const&>::type
up_cast(From const& x) { return x; }

template<class To, class From>
typename enable_if<is_base_and_derived<To,From>, To&>::type
up_cast(From& x) { return x; }

// или даже вообще
template<class T> void Function(T) { STATIC_ASSERT(false); }


А кстати! Можно же сделать вот так:
void Function(ref_t<Value const> v);

// где
template<class T>
struct ref_t
{
    T& value;
    ref_t(T& v) : value(v) {}
    // методы и операторы для доступа - добавьте по вкусу
}

Здесь мы вводим дополнительную ступень приведения типов, поэтому
Value v = 0; // int ==> Value -> cctor
Function(v); // Value& -> Value const& ==> ref_t<Value>
Function(Value(0)); // Value -> Value const& ==> ref_t<Value>
Function(0); // int ==> Value -> Value const& ==> ref_t<Value>

Знак -> соответствует "бесплатному" приведению (наложение константности, up-cast, и rvalue<->const lvalue), а ==> — платному (любой другой static_cast или подбор шаблона).
Стандарт запрещает совершать более одного платного приведения, поэтому у нас всё хорошо всюду кроме последнего.




Если же пишешь класс и хочешь избежать неверное определение функций — другое.

W>Кроме того особенности использования класса, в котором хочется это применить не позволяют плодить наследников только ради этой самой защиты )


Дык это же миксин. Он нахлобучивается поверх каждого класса в иерархии.
Как, например, в ATL — можно создать иерархию коклассов, а потом завершить каждый кокласс, превратив его в ком-объект.
... << RSDN@Home 1.2.0 alpha rev. 655>>
Перекуём баги на фичи!
Re[4]: Explicit initialization
От: Bell Россия  
Дата: 18.06.08 08:52
Оценка:
Здравствуйте, Кодт, Вы писали:

Черт возьми, неужели можно применять подобные вещи в промышленном коде, ради удовольствия писать
ExplicitTest TestObj2 = 0;

Любите книгу — источник знаний (с) М.Горький
Re[4]: Explicit initialization
От: Were  
Дата: 18.06.08 10:37
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Если же пишешь класс и хочешь избежать неверное определение функций — другое.


Я пишу класс. Другой пишет функцию, функция принимает константную ссылку на объект моего класса. Третий вызывает эту функцию. Не хочу, чтобы третий мог необдуманно ее вызвать, указав в качестве параметра что-нибудь отличное от ссылки на явно созданный им объект )

К>Дык это же миксин. Он нахлобучивается поверх каждого класса в иерархии.

Класс вида:

template < typename T1, typename T2 /*, ... */  >
class ExplicitTest
{
// ... 
};


Юзается не напрямую, а через typedef'ы:

typedef ExplicitTest< T1, T2 /*, ... */ > ExplicitTestInstance1;
typedef ExplicitTest< V1, V2 /*, ... */ > ExplicitTestInstance2;
// ...

void Function1( const ExplicitTestInstance1& );
void Function2( const ExplicitTestInstance2& );
// ...


Вообщем не очень хочется объявлять еще и типы наследников, проще сделать конструктор explicit и забить на инициализацию с помощью равно(=)
Re[5]: Explicit initialization
От: Кодт Россия  
Дата: 18.06.08 10:49
Оценка:
Здравствуйте, Bell, Вы писали:

B>Черт возьми, неужели можно применять подобные вещи в промышленном коде, ради удовольствия писать

B>
B>ExplicitTest TestObj2 = 0; 
B>

B>

Согласен со смайликом.
Но не всегда "писать" приходится "ради удовольствия".
1) могут быть всякие переходные процессы, связанные с рефакторингом (когда код инициализации ещё хочется трогать, а код вызова уже нужно)
2) нужно эксплицитно надавать по пальцам не повсеместно, а только в особых случаях
— например, разрулить неоднозначность вызова (есть несколько сигнатур Function(), и Function(0) трактуется как Function(Value(0)), вместо ошибки или вызова чего-то иного)
... << RSDN@Home 1.2.0 alpha rev. 655>>
Перекуём баги на фичи!
Re: Explicit initialization
От: Were  
Дата: 22.06.08 02:19
Оценка:
Здравствуйте, Were, Вы писали:

W>Как сделать, чтобы явной считалась и инициализация с помощью знака равно(=)?


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

template <typename T>
    class ExplicitTest
{
    // Объявим в private секции, чтоб явно не юзали )
    struct ImplicitRef
    {
        const T &m_a;
        ImplicitRef( const T &a ): m_a(a){}

    private:
        // Запрещаем присваивание, чтоб не ругался на const member
        ImplicitRef &operator =( const ImplicitRef & );
    };

    T m_a;

public:
    explicit ExplicitTest( const T &a ) :
        m_a( a ) {}

    ExplicitTest( const ExplicitTest &obj ) :
        m_a( obj.m_a ) {}

    // Самое интересное: неявное создание ImplicitRef
    // для неявной инициализации ExplicitTest.
    ExplicitTest( ImplicitRef ref ) :
        m_a( ref.m_a ) {}

    virtual ~ExplicitTest() {}
};


Тогда при использовании будет происходить следующее:

typedef ExplicitTest<int> ExplicitInt;

void Function1 ( const ExplicitInt & ref );
void Function2 ( ExplicitInt ref );

// Неявно преобразуется к виду:
// ExplicitInt I( ExplicitInt::ImplicitRef( 0 ));
ExplicitInt I = 0; // OK

// Неявно преобразуется к виду:
// Function1( ExplicitInt( 0 ));
Function1( 0 );      // Error here!!!
Function2( 0 );      // Error here!!!


Что скажете, стандарт оговаривает такое поведение или это фича MSVC?
Re[2]: Explicit initialization
От: Were  
Дата: 23.06.08 14:37
Оценка:
Здравствуйте, Were, Вы писали:

W>Что скажете, стандарт оговаривает такое поведение или это фича MSVC?

Все еще интересно мнение экспертов... )
Re[3]: Explicit initialization
От: dcb-BanDos Россия  
Дата: 24.06.08 09:06
Оценка:
Здравствуйте, Were, Вы писали:

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


W>>Что скажете, стандарт оговаривает такое поведение или это фича MSVC?

W>Все еще интересно мнение экспертов... )

"Back To The Primitive" (c)Soulfly

Имхо
Ничто не ограничивает полет мысли программиста так, как компилятор.
Re[4]: Explicit initialization
От: Were  
Дата: 24.06.08 09:29
Оценка:
Здравствуйте, dcb-BanDos, Вы писали:

W>>>Что скажете, стандарт оговаривает такое поведение или это фича MSVC?

W>>Все еще интересно мнение экспертов... )

DB>"Back To The Primitive" (c)Soulfly


DB>Имхо


Дело привычки — ничего принципиально плохого я здесь не вижу ) А по вопросу что-нибудь ответить можете? Конкретно интересует нормально-ли то, что компилятор подменяет конструктор копирования на конструктор из ImplicitRef.
И еще не совсем ясна ситуация с тем, что ImplicitRef объявлен в private секции, но при этом он нормально создается для вызова конструктора ExplicitTest.
Re[2]: Explicit initialization
От: Кодт Россия  
Дата: 24.06.08 10:07
Оценка: 4 (1)
Здравствуйте, Were, Вы писали:

W>// Неявно преобразуется к виду:
W>// ExplicitInt I( ExplicitInt::ImplicitRef( 0 ));
W>ExplicitInt I = 0; // OK

W>Что скажете, стандарт оговаривает такое поведение или это фича MSVC?

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

Для тестирования можешь проверять на Комо-онлайн




Может, всё-таки ты объяснишь, какая задача перед тобой стоит?
Я знаю несколько причин, по которым хочется достичь такого поведения:

1) громоздкий тип, хочется автоматический вывод
2) рефакторинг — нужно переделать код с минимумом крови
3) зоны доверия: "для своих" можно пользоваться неявными конструкторами, "на стороне" только явными
Но во всех случаях решения будут разными.


Скажем, для авто-вывода можно ввести промежуточный тип
template<class T>
struct approved_t
{
    T value;
    /*implicit*/ approved_t(T v) : value(v) {}
};
// для вывода типа
template<class T> approved_t<T> approved(T v) { return approved_t<T>(v); }


template<Mega List Of Params>
class YourType
{
public:
    /*implicit*/ YourType(approved_t<T> t) : something(t.value) {.....}
    explicit YourType(T v) : something(v) {.....}
};


.....

YourType<Your Mega Args> x = 123; // хочется, но нельзя
YourType<Your Mega Args> x = YourType<Your Mega Args>(123); // громоздко
YourType<Your Mega Args> x = approved(123); // можно

void fun(YourType<Your Mega Args>);
fun(123); // нельзя
fun(YourType<Your Mega Args>(123)); // громоздко
fun(approved(123)); // можно

Понятное дело, я тут не заостряюсь на передаче по значению или по ссылке, это уже второй вопрос.
... << RSDN@Home 1.2.0 alpha rev. 655>>
Перекуём баги на фичи!
Re[2]: Explicit initialization
От: Bell Россия  
Дата: 24.06.08 10:19
Оценка: 4 (1)
Здравствуйте, Were, Вы писали:

Есть достаточно надежный способ проверить, соответствует ли код стандарту — проверить компилируемость на компиляторе Comeau.
Твой пример на этом компиляторе не компилится, и я с этим согласен.
Сейчас расскажу почему:
Инициализация копированием предполагает, что в случае несовпадения типов инициализатора и создаваемого объекта, сначала будет создан временный объект с использованием одного из конструкторов инициализируемого типа, а затем этот временный объект используется в прямой инициализации в качестве параметра конструктора копирования инициализируемого типа.
Пример:
class test
{
   int i_;
public:
   test(int i) : i_(i) {}
};

test t = 1;

В этом примере сначала будет создан временный объект типа test с использованием конструктора test::test(int), а затем этот временный объект будет использован в качетсве аргумента для конструктора test::test(const test&)
Конечно, компилятор может оптимизировать этот процесс, не создавать временный объект и использовать конструктор test::test(int) "напрямую", но стандарт требует, чтобы семантические ограничения все равно принимались во внимание:

12.2/1
...
Even when the creation of the temporary object is avoided (12.8), all the semantic
restrictions must be respected as if the temporary object was created. [Example: even if the copy constructor
is not called, all the semantic restrictions, such as accessibility (clause 11), shall be satisfied. ]


Компиляторы VC (с уверенностью могу сказать про VC6, VC7.1), однако, игнорируют это требование.
Например, если в примере выше объявить конструктор копирования test::test(const test&) закрытым, то VC все равно скомпилирует пример, котя по стандарту не должен.

Мне кажется, что как раз это и является причиной того, что VC компилит вот такой упрощенный пример:
class test
{
   int i_;
public:
   test(int i) : i_(i) {}
};

class test2
{
public:
   test2(const test& t) {}
};

test2 t = 1;


"Правильная" инициализация объекта t должна происходить следующим образом:
создается временный объект test с использованием конструктора test::test(int)
создается временный объект test2 с использованием конструктора test2::test2(const test&)
объект t инициализируется с использованеим конструктора копии.

Как несложно заметить, шаги 1 и 2 — это пользовательские преобразования, а стандарт явно запрещает использование более одного пользовательского преобразования в последовательности преобразований (13.3.3.1.2/1).

VC в данном случае "напрямую" использует конструктор test2::test2(const test& t) без оглядки на конструктор копирования, и поэтому "проскакивает" ограничение на 2 пользовательских преобразования. Но такое поведение не соответствует требованиям стандарта.
По-моему так
Любите книгу — источник знаний (с) М.Горький
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.