Список переменных инициализатора

Можно ли создать initializer_list переменных, например аргументы функции (см. функцию test)?

Приведенный ниже код работает, и ни Clang, ни GCC ни на что не жалуются, но я просто хотел бы убедиться, что все в порядке.

#include <iostream>
#include <initializer_list>

template <class T>
struct array
{
    T *ptr;
    size_t len;

    array() { clear(); }
    array( T *p, size_t l ) { assign(p,l); }

    inline void clear() { ptr=nullptr; len=0; }
    inline void assign( T *p, size_t l ) { ptr=p; len=l; }

    inline T& operator[] ( size_t i ) const { return ptr[i]; }
};

template <class T>
inline array<const T> wrap( const std::initializer_list<T>& lst )
    { return array<const T>( lst.begin(), lst.size() ); }

void test( int a, int b, int c )
{
    auto ar = wrap({a,b,c});
    std::cout<< ar[2] << std::endl;
}

int main()
{
    auto a = wrap({1,2,3});
    std::cout<< a[2] << std::endl;

    test(1,2,3);
}

Побочный вопрос; если бы я попытался вернуть свой обернутый массив в test, список инициализаторов {a,b,c} вышел бы за пределы области видимости, и массив, который я возвращаю, был бы недействителен - это правильно?


person Jonathan H    schedule 16.03.2015    source источник
comment
Я думаю, что бесполезно использовать ключевое слово inline в определении шаблона. Я думаю, что я также помню из стандартов кодирования C ++ Саттера и Александреску, что часто бесполезно объявлять свои функции встроенными, поскольку это только указание для компилятора, и чаще всего компилятор умнее вас.   -  person Stephane Rolland    schedule 16.03.2015


Ответы (1)


 auto ar = wrap({a,b,c}); 

Это создает временный массив типа int[3], затем привязывает initializer_list<int> к этому массиву, затем вызывает wrap, который создает array<const int>, ссылающийся на массив.

В конце выражения массив уничтожается, оставляя array<const int> с оборванным указателем, так что это поведение undefined:

 std::cout<< ar[2] << std::endl;

Это также относится к коду в main, переменная a содержит висячий указатель, а a[2] является неопределенным поведением.

Вы можете проверить это, заменив массив int массивом типов, которые выделяют память, чтобы valgrind или asan заметили ошибку:

using V = std::vector<int>;
auto a = wrap({V{1}, V{2}, V{3}});
std::cout<< a[2].front() << std::endl;

Теперь a[2] является объектом std::vector<int>, но попытка доступа к его члену front() приводит к аварийному завершению программы:

==28356==ERROR: AddressSanitizer: heap-use-after-free on address 0x60200000efb0 at pc 0x000000401205 bp 0x7fffa46f2900 sp 0x7fffa46f28f8
READ of size 4 at 0x60200000efb0 thread T0
    #0 0x401204 in main /tmp/il.cc:28
    #1 0x3236e21d64 in __libc_start_main (/lib64/libc.so.6+0x3236e21d64)
    #2 0x400ec8  (/tmp/a.out+0x400ec8)
...

Или с валгриндом:

==28364== Invalid read of size 4
==28364==    at 0x400C72: main (il.cc:28)
==28364==  Address 0x51dfd20 is 0 bytes inside a block of size 4 free'd
==28364==    at 0x4A07991: operator delete(void*) (vg_replace_malloc.c:502)
==28364==    by 0x4013BF: __gnu_cxx::new_allocator<int>::deallocate(int*, unsigned long) (new_allocator.h:110)
==28364==    by 0x4012F8: std::allocator_traits<std::allocator<int> >::deallocate(std::allocator<int>&, int*, unsigned long) (alloc_traits.h:386)
==28364==    by 0x4011B1: std::_Vector_base<int, std::allocator<int> >::_M_deallocate(int*, unsigned long) (stl_vector.h:178)
==28364==    by 0x40102A: std::_Vector_base<int, std::allocator<int> >::~_Vector_base() (stl_vector.h:160)
==28364==    by 0x400EC4: std::vector<int, std::allocator<int> >::~vector() (stl_vector.h:425)
==28364==    by 0x400C2A: main (il.cc:27)

Побочный вопрос; если бы я попытался вернуть свой обернутый массив в тесте, список инициализаторов {a,b,c} вышел бы за пределы области видимости, и массив, который я возвращаю, был бы недействителен - это правильно?

Это уже выходит за рамки и ar уже недействительно еще до того, как вы его вернете.

person Jonathan Wakely    schedule 16.03.2015
comment
Ключевым моментом здесь является то, что время жизни временного массива, используемого initializer_list, привязано только к этому объекту; и он не распространяется на любой другой список initializer_list или другой объект, который копируется/перемещается/и т.д. из списка. - person M.M; 16.03.2015
comment
еще одно замечание: использование std::array<T, N> или std::vector<T> было бы здесь неуместным; проблема возникает из-за того, что OP array<T> содержит необработанный указатель, не являющийся владельцем, на то, с чем он был инициализирован; тогда как эти стандартные контейнеры будут владеть собственным хранилищем, которое копируется/перемещается по значению из инициализаторов. - person M.M; 16.03.2015
comment
Для уточнения в конце выражения массив уничтожается; это верно только потому, что мой initializer_list сам по себе временный, не так ли? Передача списка инициализаторов по значению, его обертка и использование обернутого значения в рамках функции будет работать, верно? - person Jonathan H; 16.03.2015
comment
Просто чтобы подтвердить то, что я только что сказал, изменение аргумента wrap на константную ссылку и запуск std::cout<< wrap({V{1}, V{2}, V{3}}) [2].front() << std::endl; работает, хотя это определенно непрактичный синтаксис. - person Jonathan H; 16.03.2015
comment
@ Sh3ljohn, нет, это правда не только из-за чего-то. Все временные объекты уничтожаются в конце полного выражения (если только они не связаны напрямую с константной ссылкой, которая живет дольше). Временный массив и оба выходят за пределы области видимости в конце выражения. Комментарий над этим работает, потому что вы используете его до конца полного выражения, прежде чем что-либо выйдет за рамки. - person Jonathan Wakely; 16.03.2015
comment
@JonathanWakely Спасибо, я не вижу никаких разногласий с тем, что я сказал в своем первом комментарии, поэтому у меня проблемы с пониманием вашего первого нет. С разными словами; проблема в том, что я пытаюсь установить указатель, который я намерен поддерживать дольше, чем время жизни его цели. Если бы вместо этого я передал initializer_list по значению какой-либо функции, вызвав локальную копию, то я смог бы постоянно обертывать его в рамках этой функции. - person Jonathan H; 16.03.2015
comment
Нет отвечал на это верно только потому, что мой .... Массив выходит за рамки, потому что он временный. Период. Тот факт, что initializer_list также является временным, не влияет на время жизни массива. Временный массив, созданный в func({1,2,3}), всегда сохраняется на время работы функции, поэтому локальная копия никак не меняет его. - person Jonathan Wakely; 16.03.2015
comment
Хм. Это различие, которое вы проводите между базовым массивом хранения и самим initializer_list, вызывает беспокойство. Если хранилище освобождается до окончания срока службы initializer_list, то почему это работает? - person Jonathan H; 16.03.2015
comment
Это совсем другая ситуация, там время жизни массива действительно продлевается. Вы ничего не сказали об использовании автоматического initializer_list, вы продолжали говорить о его передаче по значению, поэтому я подумал, что вы все еще говорите только об использовании формы f({...}), где время жизни массива ничем не продлевается. Совершенно определенно существует различие между базовым массивом хранения и initializer_list, который ссылается на него, и попытки делать странные вещи с initializer_list объектами рискованны из-за этого различия. - person Jonathan Wakely; 16.03.2015