конструктор по умолчанию и частично сформированное значение
От: andyp  
Дата: 05.08.13 19:58
Оценка: -1
При просмотре лекции Степанова наткнулся на такой код —


template <typename T, typename N = std::size_t>
class list_pool {
public:
  typedef N list_type;
  typedef T value_type;
  //...не важно
private:
  //...не важно
public:
  struct iterator {
    typedef list_pool::value_type value_type;
    typedef list_pool::list_type difference_type;    
    typedef std::forward_iterator_tag iterator_category;

    typedef value_type& reference;
    typedef value_type* pointer; 

    list_pool* pool;
    list_pool::list_type node;

    iterator() {} //!!!!! creates a partially formed value
    
    //не важно
  };
};


Степанов говорит, что для объекта вполне нормально находиться в частично сконструированном состоянии, в котором ему может быть только присвоено значение или он может быть разрушен (ну типа как источник std::move собственно после std::move), но мне как-то это не очень нравится (инварианта-то после конструирования нет). А Вы бы определили конструктор по умолчанию в этом случае?
Re: конструктор по умолчанию и частично сформированное значение
От: Abyx Россия  
Дата: 05.08.13 20:25
Оценка: :)
Здравствуйте, andyp, Вы писали:

A> list_pool* pool;

A> list_pool::list_type node;

A> iterator() {} //!!!!! creates a partially formed value


нет, это не "partially formed value".
In Zen We Trust
Re[2]: конструктор по умолчанию и частично сформированное значение
От: andyp  
Дата: 05.08.13 21:06
Оценка:
Здравствуйте, Abyx, Вы писали:

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


A>> list_pool* pool;

A>> list_pool::list_type node;

A>> iterator() {} //!!!!! creates a partially formed value


A>нет, это не "partially formed value".


не понял, разве присвоение или деструктор не сработают для такого итератора, созданного таким конструктором по умолчанию?

Вопрос несколько шире — как относиться к конструкторам, не устанавливающим инвариант вообще (Степанов видимо считает, что это ООПшное фуфло). С одной стороны — это опасно, с другой — удобно и эффективно.
Re: конструктор по умолчанию и частично сформированное значение
От: Шахтер Интернет  
Дата: 05.08.13 21:15
Оценка: 8 (2)
Здравствуйте, andyp, Вы писали: ...

То что здесь сделано по поведению подобно


int *p;


Т.е. мы конструируем объект в неопределённом состоянии, имея ввиду инициализировать его позже.

A>Степанов говорит, что для объекта вполне нормально находиться в частично сконструированном состоянии, в котором ему может быть только присвоено значение или он может быть разрушен (ну типа как источник std::move собственно после std::move), но мне как-то это не очень нравится (инварианта-то после конструирования нет). А Вы бы определили конструктор по умолчанию в этом случае?


Это очень интересный вопрос. Можно определить конструктор, создающий объект в нулевом состоянии.
На самом деле, такой конструктор по-хорошему нужен, но не дефолтный, а, например, с аргументом типа nullptr_t.

А делать ли дефолтный конструктор нулевым или неопределённым -- вопрос дискуссионный. На самом деле, компилятор, скорее всего сможет соптимизировать ненужную нулевую
начальную оптимизацию, так что можно дефолтный конструктор тоже сделать нулевым, вреда от этого не будет, а небольшая польза есть.
В XXI век с CCore.
Копай Нео, копай -- летать научишься. © Matrix. Парадоксы
Re[3]: конструктор по умолчанию и частично сформированное значение
От: Abyx Россия  
Дата: 05.08.13 21:24
Оценка:
Здравствуйте, andyp, Вы писали:

A>>> list_pool* pool;

A>>> list_pool::list_type node;

A>>> iterator() {} //!!!!! creates a partially formed value


A>>нет, это не "partially formed value".


A>не понял, разве присвоение или деструктор не сработают для такого итератора, созданного таким конструктором по умолчанию?


A>Вопрос несколько шире — как относиться к конструкторам, не устанавливающим инвариант вообще (Степанов видимо считает, что это ООПшное фуфло). С одной стороны — это опасно, с другой — удобно и эффективно.


да, "инвариант" это "ООПшное фуфло", которое существует только у Вас в голове.

для С++ объект полностью сконструирован.
если в коде нет чтения неинициализированного `pool`, то код well-formed (иначе UB)

`pool` конечно должен быть `private`, но это другой вопрос.
In Zen We Trust
Re: конструктор по умолчанию и частично сформированное значение
От: Evgeny.Panasyuk Россия  
Дата: 05.08.13 21:36
Оценка: 6 (1)
Здравствуйте, andyp, Вы писали:

A>Степанов говорит, что для объекта вполне нормально находиться в частично сконструированном состоянии, в котором ему может быть только присвоено значение или он может быть разрушен (ну типа как источник std::move собственно после std::move), но мне как-то это не очень нравится (инварианта-то после конструирования нет). А Вы бы определили конструктор по умолчанию в этом случае?


Конструктор по умолчанию, даже такой weak, бывает полезен:
Iterator x;
if(something)
   x = y;
// ...
if(!something)
   x = z;
// ...

Можно было бы конечно использовать что-то типа boost::optional — но это premature pessimization.
Единственная полезная дополнительная семантика для такого default contruction, кроме как destructible и assignable, которая приходит в голову это:
Foo x, y;
assert(x == y);
∀ z, assert(well_formed(z) == (Foo{} != z));

Например для проверки — установлено значение или нет.
Но тут я согласен с Шахтер'ом — лучше было бы иметь специальный null конструктор/значение, и причём такое null значение зачастую уже есть в виде last (если смотрели предыдущие серии Степанова про binary counting machine — там как раз используется identity передаваемая из вне).

Более того, стандарт определяет такое 24.2.1/5 поведение:

Iterators can also have singular values that are not associated with any sequence. [ Example:
After the declaration of an uninitialized pointer x ( as with int* x; ), x must always be assumed to have a
singular value of a pointer. —end example ] Results of most expressions are undefined for singular values;
the only exceptions are destroying an iterator that holds a singular value, the assignment of a non-singular
value to an iterator that holds a singular value, and, for iterators that satisfy the DefaultConstructible
requirements, using a value-initialized iterator as the source of a copy or move operation.

И ничего плохого не случилось.

Если сильно смущает, можно вставить сигнальное значение при NDEBUG (или что там в проекте), а в операции повставлять assert'ы.

P.S. Посмотри его Notes on Programming — там есть даже более weak вещи как UNDERLYING_TYPE(T).
Re[2]: конструктор по умолчанию и частично сформированное значение
От: Evgeny.Panasyuk Россия  
Дата: 05.08.13 21:42
Оценка:
Здравствуйте, Abyx, Вы писали:

A>нет, это не "partially formed value".


Elements of Programming

An object is in a partially formed state if it can be assigned to or destroyed. For an object in partially formed but not well formed, the effect of any procedure other than assignment (only on left side) and destruction is not defined.

Re[3]: конструктор по умолчанию и частично сформированное значение
От: Evgeny.Panasyuk Россия  
Дата: 05.08.13 21:46
Оценка:
Здравствуйте, andyp, Вы писали:

A>Вопрос несколько шире — как относиться к конструкторам, не устанавливающим инвариант вообще (Степанов видимо считает, что это ООПшное фуфло).


Да ладно:
vector<int> x, y;
assert(x.empty());
assert(y.empty());
assert(x == y);

Re[2]: конструктор по умолчанию и частично сформированное значение
От: andyp  
Дата: 05.08.13 21:46
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

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


A>>Степанов говорит, что для объекта вполне нормально находиться в частично сконструированном состоянии, в котором ему может быть только присвоено значение или он может быть разрушен (ну типа как источник std::move собственно после std::move), но мне как-то это не очень нравится (инварианта-то после конструирования нет). А Вы бы определили конструктор по умолчанию в этом случае?


EP>Конструктор по умолчанию, даже такой weak, бывает полезен:

EP>
EP>Iterator x;
EP>if(something)
EP>   x = y;
EP>// ...
EP>if(!something)
EP>   x = z;
EP>// ...
EP>

EP>Можно было бы конечно использовать что-то типа boost::optional — но это premature pessimization.
EP>Единственная полезная дополнительная семантика для такого default contruction, кроме как destructible и assignable, которая приходит в голову это:
EP>
EP>Foo x, y;
EP>assert(x == y);
EP>∀ z, assert(well_formed(z) == (Foo{} != z));
EP>

EP>Например для проверки — установлено значение или нет.
EP>Но тут я согласен с Шахтер'ом — лучше было бы иметь специальный null конструктор/значение, и причём такое null значение зачастую уже есть в виде last (если смотрели предыдущие серии Степанова про binary counting machine — там как раз используется identity передаваемая из вне).

EP>Более того, стандарт определяет такое 24.2.1/5 поведение:

EP>

EP>Iterators can also have singular values that are not associated with any sequence. [ Example:
EP>After the declaration of an uninitialized pointer x ( as with int* x; ), x must always be assumed to have a
EP>singular value of a pointer. —end example ] Results of most expressions are undefined for singular values;
EP>the only exceptions are destroying an iterator that holds a singular value, the assignment of a non-singular
EP>value to an iterator that holds a singular value, and, for iterators that satisfy the DefaultConstructible
EP>requirements, using a value-initialized iterator as the source of a copy or move operation.

EP>И ничего плохого не случилось.

EP>Если сильно смущает, можно вставить сигнальное значение при NDEBUG (или что там в проекте), а в операции повставлять assert'ы.


EP>P.S. Посмотри его Notes on Programming — там есть даже более weak вещи как UNDERLYING_TYPE(T).



Спасибо, что так развернуто ответили. Для себя в похожем случае я решил забить на инварианты — мне были нужены сишные массивы вещей навроде итератора.
Не очень только согласен на счет last — какой может быть last если pool-то нет. Поблема с таким итератором в том, что нет sequence, так что он по-любому будет partially initialized.
Re[4]: конструктор по умолчанию и частично сформированное значение
От: andyp  
Дата: 05.08.13 21:54
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

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


A>>Вопрос несколько шире — как относиться к конструкторам, не устанавливающим инвариант вообще (Степанов видимо считает, что это ООПшное фуфло).


EP>Да ладно:

EP>
EP>vector<int> x, y;
EP>assert(x.empty());
EP>assert(y.empty());
EP>assert(x == y);
EP>

EP>

Ну уж конструктор вектора по умолчанию устанавливает инварианты На счет последнего assert на вскидку не помню, но при наивной реализации и он должен выполняться.
Re[3]: конструктор по умолчанию и частично сформированное значение
От: Evgeny.Panasyuk Россия  
Дата: 05.08.13 22:05
Оценка:
Здравствуйте, andyp, Вы писали:

A>Спасибо, что так развернуто ответили.




A>Не очень только согласен на счет last — какой может быть last если pool-то нет.


Речь о том, что в коде, где нужны такие null-значения, доступ к last зачастую уже есть:
template<typename I, typename N>
void something(I first, I last, N n)
{
    vector<I> pivots(n, last);
    // ...
    if(pivot.front() == last)
    {
        // fill front
    }
}

И, например, в случае с binary counter — нейтральный элемент (снаружи он last) передаётся через параметр <b>z</b>.

A>Поблема с таким итератором в том, что нет sequence, так что он по-любому будет partially initialized.


Возможно есть случаи, где доступа к last нет, и параметризация не поможет, в этом случае null constructor помог бы, но тут есть два минуса:
1. приходится разрешать сравнивать элементы из range с этим null — который не из range.
2. возможно не все итераторы могут иметь "бесплатный" null. Например итератор по целым числам, а-ля boost::irange — для него ввод null value привел бы либо к дополнительному overhead'у в виде bool, либо потери одного из значений диапазона.
Re[5]: конструктор по умолчанию и частично сформированное значение
От: Evgeny.Panasyuk Россия  
Дата: 05.08.13 22:12
Оценка:
Здравствуйте, andyp, Вы писали:

A>Ну уж конструктор вектора по умолчанию устанавливает инварианты На счет последнего assert на вскидку не помню, но при наивной реализации и он должен выполняться.


stl контейнеры равны в том случае, когда равны их размеры:
distance(begin(x), end(x)) == distance(begin(y), end(y))

и когда равны соответствующие элементы range'ей:
equal(begin(x), end(x), begin(y))

begin и end всех stl контейнеров работают без pre-condition (Table 96 — Container requirements).
Re[6]: конструктор по умолчанию и частично сформированное значение
От: andyp  
Дата: 05.08.13 22:15
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

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


A>>Ну уж конструктор вектора по умолчанию устанавливает инварианты На счет последнего assert на вскидку не помню, но при наивной реализации и он должен выполняться.


EP>stl контейнеры равны в том случае, когда равны их размеры:

EP>
EP>distance(begin(x), end(x)) == distance(begin(y), end(y))
EP>

EP>и когда равны соответствующие элементы range'ей:
EP>
EP>equal(begin(x), end(x), begin(y))
EP>

EP>begin и end всех stl контейнеров работают без pre-condition (Table 96 — Container requirements).

Это и имел в виду под наивной реализацией сравнения — сравнить размеры, и если они равны, элементы.
Re[7]: конструктор по умолчанию и частично сформированное значение
От: Evgeny.Panasyuk Россия  
Дата: 05.08.13 22:27
Оценка:
Здравствуйте, andyp, Вы писали:

A>Это и имел в виду под наивной реализацией сравнения — сравнить размеры, и если они равны, элементы.


Ок, просто "наивная реализация" носит некий негативный оттенок (например "наивная реализация сортировки, работает O(N^2)").
Степанов же продумывал семантику равенства очень давно и основательно.
Re[4]: конструктор по умолчанию и частично сформированное значение
От: andyp  
Дата: 05.08.13 22:30
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

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


A>>Спасибо, что так развернуто ответили.


EP>


A>>Не очень только согласен на счет last — какой может быть last если pool-то нет.


EP>Речь о том, что в коде, где нужны такие null-значения, доступ к last зачастую уже есть:

EP>
EP>template<typename I, typename N>
EP>void something(I first, I last, N n)
EP>{
EP>    vector<I> pivots(n, last);
EP>    // ...
EP>    if(pivot.front() == last)
EP>    {
EP>        // fill front
EP>    }
EP>}
EP>

EP>И, например, в случае с binary counter — нейтральный элемент (снаружи он last) передаётся через параметр <b>z</b>.

A>>Поблема с таким итератором в том, что нет sequence, так что он по-любому будет partially initialized.


EP>Возможно есть случаи, где доступа к last нет, и параметризация не поможет, в этом случае null constructor помог бы, но тут есть два минуса:

EP>1. приходится разрешать сравнивать элементы из range с этим null — который не из range.
EP>2. возможно не все итераторы могут иметь "бесплатный" null. Например итератор по целым числам, а-ля boost::irange — для него ввод null value привел бы либо к дополнительному overhead'у в виде bool, либо потери одного из значений диапазона.

Мне кажется, что все-таки семантика будет разной при инициализации last (все-таки --last — не дает UB) и в случае null constructor. Хотя, может это все и буквоедство пустое...
Re[8]: конструктор по умолчанию и частично сформированное значение
От: andyp  
Дата: 05.08.13 22:32
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

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


A>>Это и имел в виду под наивной реализацией сравнения — сравнить размеры, и если они равны, элементы.


EP>Ок, просто "наивная реализация" носит некий негативный оттенок (например "наивная реализация сортировки, работает O(N^2)").

EP>Степанов же продумывал семантику равенства очень давно и основательно.

"наивное" == первое, что в голову пришло
Re[5]: конструктор по умолчанию и частично сформированное значение
От: Evgeny.Panasyuk Россия  
Дата: 05.08.13 22:53
Оценка:
Здравствуйте, andyp, Вы писали:

A>Мне кажется, что все-таки семантика будет разной при инициализации last (все-таки --last — не дает UB) и в случае null constructor. Хотя, может это все и буквоедство пустое...


Да, будет разной, но не из-за --last, а из-за того что last может быть "правильным" значением которое не нужно перезаписывать.
Например, нам нужно найти некий sub-range во входной последовательности:
/*...*/ something(I first, I last)
{
    I sub_first, sub_last;
    // ...
}

В этом случае last может быть не просто сигнальным значением (not-assigned), а вполне "правильным" значением, так как может быть легальной границей искомого sub-range.

То есть да, null constructor иногда был бы не заменим (позволяет обойтись без bool в некоторых случаях), но в других наоборот пришлось бы добавлять bool туда (boost::irange), где он возможно не будет использоваться.
Re[4]: конструктор по умолчанию и частично сформированное значение
От: Evgeny.Panasyuk Россия  
Дата: 05.08.13 23:05
Оценка:
Здравствуйте, Abyx, Вы писали:

A>`pool` конечно должен быть `private`, но это другой вопрос.


STL was basically communistic propaganda
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.