Подтвердить что ты не робот

Передовая практика на С++: возврат ссылки к объекту

Я пытаюсь изучить С++ и пытаюсь понять возвращающиеся объекты. Кажется, я вижу два способа сделать это, и мне нужно понять, что является лучшей практикой.

Вариант 1:

QList<Weight *> ret;
Weight *weight = new Weight(cname, "Weight");
ret.append(weight);
ret.append(c);
return &ret;

Вариант 2:

QList<Weight *> *ret = new QList();
Weight *weight = new Weight(cname, "Weight");
ret->append(weight);
ret->append(c);
return ret;

(конечно, я еще не понимаю этого).

Каким образом считается наилучшей практикой и следует придерживаться?

4b9b3361

Ответ 1

Вариант 1 неисправен. Когда вы объявляете объект

QList<Weight *> ret;

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

return ret; // no "&"

Теперь, хотя ret уничтожается, копия выполняется сначала и возвращается обратно вызывающему.

Это общепринятая методология. Фактически операция копирования и уничтожения (которая ничего не выполняет, на самом деле) обычно устранена или оптимизирована, и вы получаете быстрый, элегантный программа.

Вариант 2 работает, но тогда у вас есть указатель на кучу. Один из способов взглянуть на С++ заключается в том, что цель языка - избежать ручного управления памятью, такого как. Иногда вы хотите управлять объектами в куче, но опция 1 все еще позволяет:

QList<Weight *> *myList = new QList<Weight *>( getWeights() );

где getWeights - ваша примерная функция. (В этом случае вам может потребоваться определить конструктор копирования QList::QList( QList const & ), но, как и в предыдущем примере, он, вероятно, не будет вызван.)

Аналогично, вам, вероятно, следует избегать наличия списка указателей. Список должен хранить объекты напрямую. Попытайтесь использовать std::list... практика с языковыми особенностями важнее практики внедрения структур данных.

Ответ 2

Используйте параметр №1 с небольшим изменением; вместо того, чтобы возвращать ссылку на локально созданный объект, верните его копию.

то есть. return ret;

Большинство компиляторов С++ выполняют Оптимизацию возвращаемого значения (RVO), чтобы оптимизировать временный объект, созданный для хранения возвращаемого значения функции.

Ответ 3

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

Как уже отмечалось, ваш пример возврата по ссылке возвращает ссылку на объект, который больше не существует (поскольку он вышел из области действия) и, следовательно, вызывает поведение undefined. Вот почему вы никогда не должны возвращать ссылку. Вы никогда не должны возвращать необработанный указатель, потому что собственность неясна.

Следует также отметить, что возвращение по значению невероятно дешево из-за оптимизации возвращаемой стоимости (RVO), и скоро будет еще дешевле из-за введения ссылок rvalue.

Ответ 4

передача и возврат ссылок приглашает responsibilty.! Вам нужно позаботиться о том, чтобы при изменении некоторых значений побочных эффектов не было. то же самое в случае указателей. Я рекомендую вам перенастраивать объекты. (BUT IT VERY-MUCH DEPENDS ON WHAT EXACTLY YOU WANT TO DO)

В ur Option 1 вы возвращаете адрес и Thats VERY плохо, так как это может привести к поведению undefined. (ret будет освобожден, но y'll получит адрес ret в вызываемой функции)

поэтому используйте return ret;

Ответ 5

Как правило, плохой практикой является выделение памяти, которая должна быть освобождена в другом месте. Это одна из причин того, что у нас есть С++, а не только C. (Но опытные программисты писали объектно-ориентированный код в C задолго до Age of Stroustrup.) Хорошо построенные объекты имеют быстрые операторы копирования и присваивания (иногда используя подсчет ссылок), и они автоматически освобождают память, которую они "владеют", когда они освобождаются, и их DTOR автоматически вызывается. Таким образом, вы можете бросить их весело, вместо того, чтобы использовать указатели для них.

Поэтому, в зависимости от того, что вы хотите сделать, наилучшая практика, скорее всего, "ни одного из вышеперечисленных". Всякий раз, когда у вас возникает соблазн использовать "новое" где угодно, кроме как в CTOR, подумайте об этом. Вероятно, вы не хотите использовать "новое" вообще. Если вы это сделаете, полученный указатель, вероятно, должен быть обернут каким-то умным указателем. Вы можете идти неделями и месяцами, не называя "новых", потому что "новые" и "удаленные" позаботятся в стандартных классах или шаблонах классов, таких как std:: list и std::vector.

Единственное исключение - когда вы используете библиотеку старой моды, такую ​​как OpenCV, которая иногда требует создания нового объекта и отсылает указатель на нее в систему, которая переходит в собственность.

Если QList и Weight правильно записаны для очистки после себя в своих DTORS, то вы хотите,

QList<Weight> ret();
Weight weight(cname, "Weight");
ret.append(weight);
ret.append(c);
return ret;

Ответ 6

Как уже упоминалось, лучше избегать выделения памяти, которая должна быть освобождена в другом месте. Это то, что я предпочитаю делать (... в эти дни):

void someFunc(QList<Weight *>& list){
    // ... other code
    Weight *weight = new Weight(cname, "Weight");
    list.append(weight);
    list.append(c);
}

// ... later ...

QList<Weight *> list;
someFunc(list)

Еще лучше - полностью избегайте new и используя std::vector:

void someFunc(std::vector<Weight>& list){
    // ... other code
    Weight weight(cname, "Weight");
    list.push_back(weight);
    list.push_back(c);
}

// ... later ...

std::vector<Weight> list;
someFunc(list);

Вы всегда можете использовать bool или enum, если хотите вернуть флаг состояния.

Ответ 7

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

Если вы хотите избежать копирования, вы можете пойти на реализацию класса Вес с конструктором копирования, а оператор копирования отключен:

class Weight { 
protected:
    std::string name;
    std::string desc;
public:
    Weight (std::string n, std::string d) 
        : name(n), desc(d) {
        std::cout << "W c-tor\n"; 
    }
    ~Weight (void) {
        std::cout << "W d-tor\n"; 
    }

    // disable them to prevent copying
    // and generate error when compiling
    Weight(const Weight&);
    void operator=(const Weight&);
};

Затем для класса, реализующего контейнер, используйте shared_ptr или unique_ptr для реализации элемента данных:

template <typename T>
class QList {
protected:
    std::vector<std::shared_ptr<T>> v;
public:
    QList (void) { 
        std::cout << "Q c-tor\n"; 
    }
    ~QList (void) { 
        std::cout << "Q d-tor\n"; 
    }

    // disable them to prevent copying
    QList(const QList&);
    void operator=(const QList&);

    void append(T& t) {
        v.push_back(std::shared_ptr<T>(&t));
    }
};

Ваша функция для добавления элемента будет использовать Оптимизацию значений возвращаемого значения и не будет вызывать конструктор копирования (который не определен):

QList<Weight> create (void) {
    QList<Weight> ret;
    Weight& weight = *(new Weight("cname", "Weight"));
    ret.append(weight);
    return ret;
}

При добавлении элемента, пусть контейнер берет собственность на объект, поэтому не освобождайте его:

QList<Weight> ql = create();
ql.append(*(new Weight("aname", "Height")));

// this generates segmentation fault because
// the object would be deallocated twice
Weight w("aname", "Height");
ql.append(w);

Или, лучше, заставить пользователя передать вашу реализацию QList только умные указатели:

void append(std::shared_ptr<T> t) {
    v.push_back(t);
}

И вне класса QList вы будете использовать его как:

Weight * pw = new Weight("aname", "Height");
ql.append(std::shared_ptr<Weight>(pw));

Используя shared_ptr, вы также можете "брать" объекты из коллекции, делать копии, удалять из коллекции, но использовать локально - за кулисами это будет только один и тот же единственный объект.

Ответ 8

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

Если вы создаете "простую" программу (хотя "простая" означает, что вы должны идти с java или С#), то используйте конструкторы копирования, избегайте указателей и используйте интеллектуальные указатели для освобождения используемой памяти, если вы создаете сложные программы или вам нужна хорошая производительность, используйте указатели повсюду и избегайте создания копий (если это возможно), просто создайте свой набор правил для удаления указателей и используйте valgrind для обнаружения утечек памяти,

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

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

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