2016-04-20 4 views
4

Недавно у меня возникла очень странная проблема с кодом на C++. Я воспроизвел случай в минималистском примере. У нас есть класс Яйцо:C++ ostringstream странное поведение

class Egg 
{ 
private: 
    const char* name; 
public: 
    Egg() {}; 
    Egg(const char* name) { 
     this->name=name; 
    } 
    const char* getName() { 
     return name; 
    } 
}; 

Мы также имеем класс корзины для хранения яиц

const int size = 15; 
class Basket 
{ 
private: 
    int currentSize=0; 
    Egg* eggs; 
public: 
    Basket(){ 
     eggs=new Egg[size]; 
    } 
    void addEgg(Egg e){ 
     eggs[currentSize]=e; 
     currentSize++; 
    } 
    void printEggs(){ 
     for(int i=0; i<currentSize; i++) 
     { 
      cout<<eggs[i].getName()<<endl; 
     } 
    } 
    ~Basket(){ 
     delete[] eggs; 
    } 
}; 

Так вот пример, который работает, как ожидалось.

Basket basket; 
Egg egg1("Egg1"); 
Egg egg2("Egg2"); 

basket.addEgg(egg1); 
basket.addEgg(egg2); 
basket.printEggs(); 
//Output: Egg1 Egg2 

Это ожидаемый результат, но если я хочу добавить N яйцо с сгенерированными именами в зависимости от некоторых переменного цикла, У меня есть следующие проблемы.

Basket basket; 
for(int i = 0; i<2; i++) { 
    ostringstream os; 
    os<<"Egg"<<i; 
    Egg egg(os.str().c_str()); 
    basket.addEgg(egg); 
} 
basket.printEggs(); 
//Output: Egg1 Egg1 

Если изменить условие цикла, чтобы я < 5, я получаю "Egg4 Egg4 Egg4 Egg4 Egg4". Он сохраняет последнее добавленное яйцо во всех индексах динамического массива Egg.

После некоторого поиска в google я обнаружил, что задание переменной char * name в Egg фиксированного размера и использование strcpy в конструкторе устраняет проблему.

Вот «фиксированный» класс яйца.

class Egg 
{ 
private: 
    char name[50]; 
public: 
    Egg(){}; 
    Egg(const char* name) 
    { 
     strcpy(this->name, name); 
    } 
    const char* getName() 
    { 
     return name; 
    } 
}; 

Теперь вопрос в том, почему? : D

Заранее спасибо.

Here является ссылкой на весь код.

+4

Поскольку вы используете C++, гораздо лучше использовать 'std :: string', чем старые функции строки C. – tadman

+0

Да, я не так хорош с C++. Просто хотел создать пример для случая –

+1

Согласен, используйте 'std :: string' и' std :: vector' + напишите свой собственный конструктор копий, так как я предполагаю, что вы испытываете неопределенное поведение. – JVApen

ответ

5

Давайте более подробно рассмотрим это выражение: os.str().c_str().

Функция str возвращает строку по значению, и используя его таким образом сделать возвращаемую строку объектом временного которого срок службы не только до конца выражения. Как только выражение заканчивается, строковый объект разрушается и больше не существует.

Указатель, который вы передаете конструктору, является указателем на внутреннюю строку временного строкового объекта. Как только объект string разрушен, указатель больше не действует, и его использование приведет к неопределенным поведением.

Простым решением является, конечно, использование std::string всякий раз, когда вы хотите использовать строку. Более сложное решение состоит в том, чтобы использовать массив и скопировать содержимое строки, прежде чем оно исчезнет (как в «фиксированном» классе Egg). Но имейте в виду, что «фиксированное» решение с использованием массивов фиксированного размера подвержено переполнению буфера.

+0

Спасибо, это многое прояснилось, так что получается, что «ostringstream» использует одно и то же место в памяти каждый раз, когда он создан? Так как он воссоздан на каждой итерации. И еще одно, вы говорите, что мы получаем неопределенное поведение, но почему, поскольку я скопирую указатель в массиве правильно? Переменная в цикле уничтожается, но копия хранится в массиве яиц (который получается не копией, а тем же указателем). –

+1

@BorislavStoilov Проблема в том, что ваша переменная является указателем. Указатель ничего не делает, но указывает на ** другую ** переменную.И **, что ** переменная разрушена. Любая попытка получить к нему доступ через этот указатель вызывает UB –

+1

@BorislavStoilov Компилятор должен выделять пространство для объекта 'ostringstream' для каждой итерации цикла, но зачем тратить драгоценные циклы на это, когда он может просто повторно использовать пространство на каждой итерации? Поэтому да, он просто повторно использует одно и то же пространство на каждой итерации и просто вызывает конструктор/деструктор. –

2

В вашем первом случае вы копия указатель, указывающий на строку.

Во втором случае, с strcpy(), вы на самом деле глубокая копия строка.


ОК, я не был подробным, позвольте мне уточнить. В первом случае вы копируете указатель, который указывает на строку, созданную с помощью ostringstream. Что происходит, когда это выходит за рамки?

Неопределенное поведение!

+0

Но не совсем ли указатель? В цикле «Яйцо яйцо (os.str(). C_str());», каждый раз, когда создается новое яйцо? –

+0

@BorislavStoilov Я обновил, извините. Да, но, поскольку вы испытываете UB, мы не можем точно сказать, что такое поведение. Я бы предположил, что функция повторно использует свою память, поэтому последняя копия остается в живых. Хороший вопрос BTW, +1. – gsamaras

1

os.str() является анонимный временный типа std::string, и поведение на доступ к памяти, на которую указывает .c_str(), как только что анонимный временный выходит из области видимости (что делает в конце заявления), является не определен. Ваш второй случай работает с strcpy(this->name, name);, и копия данных, на которые указывает .c_str(), до временного выхода из сферы действия. Но код по-прежнему хрупкий: буфер символа фиксированного размера уязвим для переполнения. (Тривиальное исправление будет заключаться в использовании strncpy).

Но исправить правильно, использовать стандартную библиотеку C++: использовать std::string в качестве типа для name, const std::string& в качестве возвращаемого типа для getName и контейнера как std::list<Egg> держать яйцо в корзине.

0

Вы не копируете строку в свой конструктор Egg, просто указатель, который является начальным адресом строки.

Случалось, что все экземпляры ваших острий выделяют свои буферы в одном и том же месте снова и снова. И случилось, что буфер не перезаписывался между контуром построения for и контуром вывода for.

Вот почему все ваши Egg s имеют указатели name, указывающие на то же место, и это место содержит фамилию, построенную.

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