2015-03-16 3 views
1

Можно ли создать переменные типа initializer_list, например, аргументы функции (см. Функцию test)? Код ниже работает, и ни clang, ни gcc не жалуются ни на что, но я просто хотел бы убедиться, что все в порядке.Список инициализаторов переменных

редактировать: изменения аргумента wrap к константной исх

#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} выйдет из области видимости, и массив, который я возвращаю, будет недействительным - это правильно?

+0

Я считаю, что использовать определение 'inline' в определении шаблона бесполезно. Я думаю, что я также помню из «Стандартов кодирования C++» Sutter и Alexandrescu, что часто бесполезно объявлять функции inline, так как это только указание для компилятора, и чаще всего компилятор умнее вас. –

ответ

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

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

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

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

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

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

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) 
... 

Или с Valgrind:

==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 уже недействителен, даже если вы его не вернете.

+1

Ключевым моментом здесь является то, что время жизни временного массива, используемого 'initializer_list', привязано только к этому объекту; и он * не * распространяется на любой другой файл initializer_list или другой объект, который копируется/перемещается/и т. д. из списка. –

+1

другое примечание: использование 'std :: array ', или 'std :: vector ' будет hunky dory здесь; проблема возникает из-за того, что '' массив '' хранит необоснованный необработанный указатель на то, что было инициализировано; тогда как эти стандартные контейнеры будут иметь собственное хранилище, которое копируется/перемещается по значению из инициализаторов. –

+0

Чтобы уточнить «в конце выражения массив уничтожен»; это верно только потому, что мой 'initializer_list' сам по себе является временным, не так ли? Передача списка инициализатора по значению, его перенос и использование обернутого значения, в то время как в рамках функции будет работать, правильно? – Sheljohn

Смежные вопросы