Нужно ли создавать контейнеры (наборы, векторы и т. Д.) С помощью ключевого слова new, чтобы сохранить его в разных функциях? - PullRequest
0 голосов
/ 16 апреля 2011

Я работал над школьным заданием, в котором довольно интенсивно используются векторы, наборы, стеки и очереди.

В чем разница между Foo и Bar, особенно при прохождении Foo и Bar между функциями?Когда будет безопасно вызывать delete на панели, если вообще?Я думаю, никогда не безопасно вызывать delete на Bar, если все в этом векторе не было перемещено?Если я верну Foo, не будет ли он (и его содержимое) удален при выходе из функции?

vector<Line *> Foo;

и:

vector<Line *> * Bar = new vector<Line *>();

Похоже, у меня есть функция:

vector<Line *> BSTIndex::query(string expression)
{
    vector<Line *> result; //Holds the lines that match the expression
    string query = expression;
    queue<string> * output = expressionParser(query);
    doSomeStuffWithOutputHere();
    return result;
}

И мой expressionParser:

queue<string> * BSTIndex::expressionParser(string query)
{
    char * cQuery = new char[100];
    strcpy_s(cQuery, 100,query.c_str());
    //strcpy(cQuery, query.c_str());
    queue<string> * output = new queue<string>(); //Operators go in the queue
    stack<string> * s = new stack<string>();  //Operands go on the stack
    performSomeMagicOnQueueAndStackHere();
    return output;
}

На самом деле стек является только локальным для expressionParser, поэтому я ЗНАЮ, что могу удалить из него новое ключевое слово.Очередь, однако, должна вернуться к функции запроса, где она используется, но тогда это все.Должен ли я создать указатель на очередь в этом случае (я хочу сказать «да», потому что он выйдет из области видимости, когда expressionParser вернется).Если мне нужно создать указатель, то я должен вызвать вывод удаления в моей функции запроса, чтобы правильно избавиться от очереди?

Мое последнее беспокойство - вектор, возвращаемый запросом.Должен ли он быть оставлен как есть, или это указатель, и какая разница между ними?Как только я использую этот вектор (отображаю информацию в нем), мне нужно, чтобы он был удален из памяти, однако указатели, содержащиеся в векторе, все еще хороши, и элементы, на которые они указывают, не должны быть удалены.Каков наилучший подход в этом случае?

Что происходит с содержимым контейнеров, когда вы вызываете delete для контейнера?Если в контейнере содержатся указатели и эти указатели удалены, это не повлияет на другие области моей программы?

Ответы [ 4 ]

5 голосов
/ 16 апреля 2011

ТАК много вопросов (прямых и косвенных) втиснуто в такое маленькое пространство!

В чем разница между Foo и Bar.

vector<Line*>    Foo;
vector<Line*>*   Bar = new vector<Line*>();

Foo является объектом автоматического (потенциально статического (но здесь не важного различия)) срока хранения. Это означает, что он создается в точке объявления (вызываемые конструкторы) и уничтожается при выходе из области видимости (вызываемые деструкторы).

Bar с другой стороны, это указатель. Указатель не имеет конструкторов или деструкторов и используется для point в других объектах (или NULL). Здесь Bar инициализируется так, чтобы указывать на объект динамического хранения. Это объект, который необходимо разблокировать вручную (в противном случае он будет вытекать).

особенно при передаче Foo и Bar между функциями?

При передаче Foo (по значению) в / из функций конструктор копирования используется для создания копии исходного объекта. Примечание. В стандарте прямо указано, что копирование из функции (с помощью возврата) может быть исключено (см. RVO / NRVO), и все современные компиляторы удаляют дополнительную конструкцию копирования и создают ее на месте возврата (но это невидимая оптимизация для пользователя просто думать об этом как очень эффективная копия из функции). Результатом этого является то, что при передаче в функцию (по значению) или возвращении из функции (по возврату) вы работаете с новым объектом (а не с оригиналом). Это важно, поскольку побочные эффекты от использования объекта не повлияют на оригинал. (см. ваш вопрос ниже о возвращении Foo).

При передаче Bar (по значению) указатель копируется. Но это означает, что то, на что указывают, то же самое. Таким образом, изменение объекта с помощью указателя - это изменение исходного значения. Это делает передачу его в качестве параметра очень дешевой (потому что все, что вы передаете, это адрес объекта). Но это делает возвращение значения потенциально опасным, потому что вы можете вернуть адрес объекта, который вышел из области видимости внутри функции.

Использование указателей в опасных по своей природе и современных программах на C ++ редко использует RAW-указатели напрямую. Указатели обычно заключаются в объекты, которые управляют владением и потенциальной продолжительностью жизни объекта (см. Интеллектуальные указатели и контейнеры).

Когда было бы безопасно вызывать delete на панели, если вообще?

Безопасно удалять Bar только если:

  • НУЛЬ
  • Объект, на который он указывает, был динамически распределен через new.

Полагаю, никогда не безопасно вызывать delete на Bar, если все в этом векторе не было перемещено?

Безопасно было бы удалить Bar, даже если его содержимое не было перемещено (хотя вы можете утечь, если содержащиеся в нем указатели принадлежат Bar (это не является небезопасным, но может быть неудобно, когда вы исчерпываете пространство )). Это еще одна причина, по которой указатели редко используются напрямую, семантика владения, связанная с указателем, отсутствует. Это означает, что мы не можем сказать, владеет ли Bar указателями, которые он содержит (владелец обязан вызывать delete для указателя, когда он больше не используется).

Если я верну Foo, не будет ли он (и его содержимое) удален при выходе из функции?

Да. Но поскольку вы возвращаете объект, он будет скопирован из функции. То, что вы используете вне функции - это копия Foo (хотя оптимизаторы могут исключить копию из-за RVO / NRVO. Нет, если копия исключена, деструктор также исключается).

Похоже, допустим, у меня есть функция: query и expressionParser:

vector<Line *> BSTIndex::query(string expression)
{
    vector<Line *> result; //Holds the lines that match the expression
    string query = expression;
    queue<string> * output = expressionParser(query);
    doSomeStuffWithOutputHere();
    return result;
}

queue<string> * BSTIndex::expressionParser(string query)
{
    char*    cQuery = new char[100];
    strcpy_s(cQuery, 100,query.c_str());
    queue<string> * output = new queue<string>(); //Operators go in the queue
    stack<string> * s = new stack<string>();  //Operands go on the stack
    performSomeMagicOnQueueAndStackHere();
    return output;
}

Стек на самом деле является только локальным для expressionParser, поэтому я ЗНАЮ, что могу удалить из него новое ключевое слово.

Не только вы, но и вы должны. Как и сейчас, вы вытекаете из объекта, когда он выходит из области видимости.

Очередь, однако, должна вернуться к функции запроса, где она используется, но тогда это все.Должен ли я создать указатель на очередь в этом случае (я хочу сказать «да», потому что он выйдет из области видимости, когда expressionParser вернется).

Нет.Вы можете передать объект обратно по значению.Он будет правильно скопирован из функции.Если вы обнаружите, что это дорого (маловероятно), вы можете создать динамический объект с новым и передать его обратно в качестве указателя (но вы можете захотеть взглянуть на умные указатели).Помните, что указатели не указывают на то, что вы являетесь владельцем, поэтому неясно, кто должен удалить указатель (или даже если он должен быть удален).Поэтому (если передача значения слишком дорогая), используйте std :: auto_ptr <> и передайте указатель обратно внутрь него.

Если мне нужно создать указатель, то я должен вызвать вывод удаленияв моей функции запроса, чтобы правильно избавиться от очереди?

Да.И нет. Если вы создаете dynamic object с новым.Тогда кто-то должен вызвать удаление на этом.Это плохой стиль C ++, чтобы делать это вручную.Научитесь использовать умные указатели, чтобы это делалось автоматически и в безопасном особняке исключений.

Мое последнее беспокойство - вектор, возвращаемый запросом.Должно ли это быть так, как у меня, или это указатель и в чем разница между ними?

Я бы сделал это так:

vector<Line> BSTIndex::query(string const& expression)
{
    vector<Line>    result;       // 1 Keep line objects not pointers.
    queue<string>   output  = expressionParser(expression);

    doSomeStuffWithOutputHere();

    return result;
}

queue<string> BSTIndex::expressionParser(string const& query)
{
    char    cQuery[100];               // Don't dynamically allocate
                                       // unless somebody takes ownership.
                                       // It would probably be better to use string or vector
                                       // depending on what you want to do.

    strcpy_s(cQuery, 100, query.c_str()); // std::string can use the assignment operator.

    queue<string>   output;            // Just build the que and use it.
    stack<string>   s;                 // Unless there is a need. Just use a local one.

    performSomeMagicOnQueueAndStackHere();

    return output;
}

Один разЯ использую этот вектор (отображаю информацию в нем), мне нужно, чтобы он был удален из памяти, однако указатели, содержащиеся в векторе, все еще хороши, и элементы, на которые они указывают, не должны быть удалены.Каков наилучший подход в этом случае?

Зависит от того, кто владеет указателями.

Что происходит с содержимым контейнеров, когда вы вызываете delete для контейнера?

Если содержимое контейнеров является указателями.Тогда ничего.Указатели исчезают (то, на что они указывают, остается неизменным и неизменным).Если контейнер содержит объекты (не указатели), то для объектов вызывается деструктор.

Если контейнер содержит указатели, и эти указатели удаляются, не повлияет ли это на другие области моей программы?

Было бы.Но вам придется вызвать удаление вручную.

2 голосов
/ 16 апреля 2011

По большей части, что вы обычно хотите сделать, это просто создать экземпляр рассматриваемого объекта, а когда вам нужно его вернуть, вернуть его.

Для чего-то вроде большого контейнера, этоможет (и часто звучит) так, как если бы это было ужасно неэффективно.В действительности, однако, это обычно вполне эффективно.В стандарте C ++ есть пара предложений, которые допускают так называемую оптимизацию возвращаемых значений (RVO) и оптимизацию именованных возвращаемых значений (NRVO).Первоначально то, что в стандарте, звучит не очень важно - просто сказано, что компилятору не нужно вызывать конструктор копирования для возвращаемого значения, даже если конструктор копирования имеет побочные эффекты, поэтому вы можете видеть, что он был опущен.

Однако в действительности это означает, что компилятор (то есть, по существу, все разумно текущие компиляторы) фактически создаст только одну коллекцию на самом высоком уровне.уровень в иерархии вызовов функций, где он используется.Когда вы в конечном итоге возвращаете объект этого типа из какой-то другой функции, вместо того, чтобы создавать локальную копию и затем копировать ее для возврата, она просто сгенерирует код в вызываемой функции, который работает непосредственно в коллекции в вызывающей функции.Таким образом, возврат в основном становится NOP.В результате вместо ужасно неэффективного копирования, которое выглядит, вы получите действительно быстрый возврат без копирования вообще.

1 голос
/ 16 апреля 2011

(1) На самом деле стек является только локальным для expressionParser, поэтому я ЗНАЮ, что могу удалить новое ключевое слово

правильный

(2) Должен ли я создать указатель на очередь в этом случае. Если мне нужно создать указатель, то я должен вызвать вывод удаления в моей функции запроса, чтобы правильно избавиться от очереди?

правильно. Здесь «создание указателя» больше не означает использование new и копирование всего содержимого. Просто назначьте указатель на уже созданный queue<string>. Просто delete output;, когда вы закончите с этим в вашем query().

(3) вектор, возвращаемый по запросу. Должен ли он быть оставлен как есть, или это указатель, и в чем разница между ними?

Я бы предложил либо передать vector<Line*> по неконстантной ссылке на query() и использовать его, либо вернуть vector<Line*>* из вашей функции. Разница в том, что если вектор огромен и возвращается по значению, он может скопировать весь свой контент (учитывая наихудший случай без оптимизации)

(4) Как только я использую этот вектор (отображаю информацию в нем), мне нужно удалить его из памяти, однако указатели, содержащиеся в векторе, все еще хороши, и элементы, на которые они указывают, должны не быть удаленным. Каков наилучший подход в этом случае?

Передайте vector<Line*> в функцию по ссылке. И сохраняйте его в объеме, пока он вам не понадобится. Как только вы закончите со строкой указателей членов *inside it, just, удалите их

(5) Что происходит с содержимым контейнеров, когда вы вызываете delete для контейнера? Если в контейнере хранятся указатели?

Ничего не происходит с указателями членов контейнера при его удалении. Вы должны явно позвонить delete на каждого участника. Кстати, в вашем случае вы не можете delete ваш vector<Line*>, потому что это объект, а не указатель, и когда вы delete output;, вам не нужно беспокоиться о содержимом queue<>, потому что они не являются указателями .

(6) Эти указатели удалены, не повлияет ли это на другие области моей программы?

Удаление действительного указателя (т.е. выделенного в куче) никак не повлияет на вашу программу.

0 голосов
/ 16 апреля 2011

У вас здесь логическая проблема - std::vector (или любой контейнер в этом отношении) указателей, при уничтожении, будут убивать только эти указатели, не , на что они указываютк.Таким образом, все в порядке:

#include <vector>
#include <iostream>

std::vector<int*> GetIntPtrVec(){
  std::vector<int*> ret(10); // prepare for 10 pointer;
  for(int i=0; i<10; ++i){
    ret[i] = new int(i);
  }
}

int main(){
  std::vector<int*> myvec = GetIntPtrVec(); // copies the return into myvec
//                  ^^^^^^^^^^^^^^^^^^^^^^

  for(int i=0; i<myvec.size(); ++i){
    // display and delete again
    std::cout << myvec[i] << "\n";
    delete myvec[i];
  }

  std::cin.get();
}

При возврате вектора из функции он копируется в получающую переменную (отмеченную ^ выше).
Затем для указателя на-векторный подход, конечно, вам нужно когда-нибудь снова удалить этот указатель - когда вы можете быть уверены, что никто его больше не использует.Но та же проблема относится и к первому подходу - когда вы собираетесь удалить указатели внутри возвращаемого вектора?Вы должны убедиться, что нет висящих указателей, например указателя, которые указывают на вашу теперь удаленную память.

...