2015-01-21 2 views
1

В C++ 11, std::function is MoveConstructible, то есть можно реально вызвать std::move на таких объектах или сохранить их в подвижных типах. Недостаток: что должен напечатать следующий код?Должен ли std :: function :: operator bool возвращать false после перемещения?

#include <stdio.h> 

#include <functional> 
#include <utility> 

struct Big { 
    char data[1024]; 
}; 

int main(int argc, char **argv) { 
    Big blob; 
    // This bind will trigger small object optimization 
    std::function<void()> little = std::bind([]() { printf("little\n"); }); 
    // This bind will not 
    std::function<void()> big = std::bind([](Big const& b) { 
      printf("big %c\n", b.data[0]); 
     }, blob); 
    auto little_moved = std::move(little); 
    auto big_moved = std::move(big); 

    // After move, one expects the source std::function to be empty 
    // (boolean value false) 

    printf("Little empty: %d\n", !little); 
    printf("Little (moved) empty: %d\n", !little_moved); 
    printf("Big empty: %d\n", !big); 
    printf("Big (moved) empty: %d\n", !big_moved); 

    return 0; 
} 

Собран с GCC 4.8, вы получите это:

linux-dev:nater:/tmp$ g++-4.8 -g -o foo move_function.cc -std=c++11 
linux-dev:nater:/tmp$ ./foo 
Little empty: 1 
Little (moved) empty: 0 
Big empty: 1 
Big (moved) empty: 0 

объект ведет себя, как и ожидалось, недействительности RHS о назначении перемещения. Однако, не все так ясно, с лязгом (Apple LLVM версии 6.0):

workbrick:nater:/tmp$ clang++ -g -o foo move_function.cc -std=c++11 -stdlib=libc++ 
workbrick:nater:/tmp$ ./foo 
Little empty: 0 
Little (moved) empty: 0 
Big empty: 1 
Big (moved) empty: 0 

Здесь RHS аннулируется (ложь в логическом контексте) после того, как двигаться, когда связанные параметры велики, но не тогда, когда связаны параметры являются небольшими (технически, несуществующими). Рассматривая реализацию <functional> поставляется с Xcode, мы видим, что поведение различается в зависимости от того, была ли применена небольшая оптимизация объекта:

template<class _Rp, class ..._ArgTypes> 
template <class _Alloc> 
function<_Rp(_ArgTypes...)>::function(allocator_arg_t, const _Alloc&, 
            function&& __f) 
{ 
    if (__f.__f_ == 0) 
     __f_ = 0; 
    else if (__f.__f_ == (__base*)&__f.__buf_) 
    { 
     // [nater] in this optimization, __f.__f_ is not invalidate 
     __f_ = (__base*)&__buf_; 
     __f.__f_->__clone(__f_); 
    } 
    else 
    { 
     // [nater] here, the RHS gets invalidated 
     __f_ = __f.__f_; 
     __f.__f_ = 0; 
    } 
} 

Теперь я знаю, что состояние ОРЗ после присвоения перемещения является тип- но я удивил, что поведение этого STL-класса несовместимо. Это действительно неопределенное в спецификации?

+0

Действительно, так оно и есть. Для потомков: _ (6) effects: If! F, * у этого нет цели; в противном случае move-конструирует цель из f в цель * этого, оставляя f в допустимом состоянии с неопределенным значением ._ Не стесняйтесь распространять это на ответ, и я его приму. –

ответ

0

Не определено, какое значение имеет объект, после его перемещения.

Кроме того, это некоторая допустимая ценность, конечно.

Есть несколько общих случаев:

  1. источника нетронутый, если копирование дешево и не может бросить.
  2. Источник остается в состоянии, построенном по умолчанию.
  3. Источник остается с любым значением, которое ранее было у цели. (Он был заменен.)
  4. Источник находится в некотором поврежденном состоянии, которое можно безопасно уничтожить и назначить.
1

Пристыженный в это, глядя вверх DYP-х helpful comment, это действительно не определено поведение, будет ли !func быть правдой или нет после auto foo = std::move(func) (или любой другой шаг, от на объекте). Соответствующий текст из спецификации C++ 11:

(6) effects: If! F, * у этого нет цели; в противном случае, движение-конструирует цель е в мишень * это, в результате чего п в допустимом состоянии с неустановленным значением

Другой победой для неопределенных неустановленного поведения.

+0

В этом контексте немного странно видеть термин «неопределенное поведение». Да, он «не определен», если объект функции пуст, но никакое Undefined Behavior не вызывается функцией move-construction (таким образом). (UB дает возможность делать что-либо, тогда как возможное поведение здесь ограничено «действительным состоянием».) – dyp

+0

Ну, семантика перемещенной из 'std :: function' не определена, даже если состояние объекта равно" действительны ", не так ли? То есть, язык в стандарте дает широту разработчикам библиотек, чтобы сделать вызов 'operator bool' или' operator() 'иметь какой-либо из их (действительных) эффектов, а в случае реализации clang наблюдаются два поведения, в зависимости от тип обернутого * Callable *. Перемещенный объект не пуст в одном случае и пуст в другом. Возможно, нам нужна новая категория поведения, которая не является «неопределенной», но ее все еще невозможно узнать;) –

+1

Тонкая разница между «неопределенным поведением» и «неуказанным поведением» заключается в том, что последний «обычно определяется этим [C++] международным стандартом msgstr "[defns.unspecified]. Термин «неопределенное поведение» предполагает, что 'operator bool' может генерировать исключение или стирать жесткий диск в соответствующей (но глупой) реализации. – dyp