Russian Qt Forum

Qt => Общие вопросы => Тема начата: vanessa от Январь 18, 2010, 23:13



Название: Работа с контейнерами - терзают сомненья
Отправлено: vanessa от Январь 18, 2010, 23:13
В силу не очень хорошего знания С++ хочу задать вопрос
Код:
#include <QtCore>
#include <QDebug>


QVector<int> getValue()
{
    QVector<int> vec;
    vec << 10 << 11 << 12;
    return vec;
}



int main(int argc,char **argv)
{
    QVector<int> a;
    a << 1 << 2 << 3;

    a=getValue();
    qDebug()<<a;

    return 0;
}


не будет ли утечки памяти в строчке
    a=getValue();
тоесть будет ли удалена или перераспределена память используемая для хранения значений a в момент присвоения этой переменной нового значения ?
и вообще можно ти делать такие функции, которые будут приниматть или возвращать контейнеры, не является ли это плохим стилем ?



Название: Re: Работа с контейнерами - терзают сомненья
Отправлено: Пантер от Январь 18, 2010, 23:25
Утечки не будет.


Название: Re: Работа с контейнерами - терзают сомненья
Отправлено: lit-uriy от Январь 18, 2010, 23:37
>>будет ли удалена или перераспределена память используемая для хранения значений a
В данном случае будет, т.к. в контейнере хранятся значения. Подробности о том, как разные типы данных хранятся в контейнерах смотри тут (http://www.doc.crossplatform.ru/qt/4.4.3/containers.html#the-container-classes)

>>не является ли это плохим стилем ?
Посмотри на QObject::findChildren и ей подобные функции.


Название: Re: Работа с контейнерами - терзают сомненья
Отправлено: niXman от Январь 19, 2010, 00:52
Цитировать
В данном случае будет, т.к. в контейнере хранятся значения.
поясните.


Название: Re: Работа с контейнерами - терзают сомненья
Отправлено: lit-uriy от Январь 19, 2010, 08:20
еже либ там были указатели на объекты, то объекты надо было бы прибивать руками, т.к. контейнер сам этим не занимается.


Название: Re: Работа с контейнерами - терзают сомненья
Отправлено: Marat(Qt) от Январь 20, 2010, 19:27
еже либ там были указатели на объекты, то объекты надо было бы прибивать руками, т.к. контейнер сам этим не занимается.
Код:
QVector<T> &QVector<T>::operator=(const QVector<T> &v)
{
    v.d->ref.ref();
    if (!d->ref.deref())
        free(d);
    d = v.d;
    if (!d->sharable)
        detach_helper();
    return *this;
}
А как такое объяснить? Вектор очищяется перед заполнением новыми значениями, следовательно - нет утечки


Название: Re: Работа с контейнерами - терзают сомненья
Отправлено: Авварон от Январь 20, 2010, 20:26
удаляется d_ptr, а данные - нет


Название: Re: Работа с контейнерами - терзают сомненья
Отправлено: lit-uriy от Январь 20, 2010, 20:40
>>Вектор очищяется
именно вектор, а не объекты созданные с помощью new.

Из документации по Tulip:
Цитировать
Контейнеры Tulip содержат значения. Если вы хотите использовать список в котором каждое значение - это QWidget *, используйте QList<QWidget *>.

Новые контейнеры не поддерживают автоудаления. Мы обнаружили, что на практике, единственным случаем, где нужно автоудаление является ситуация, где в контейнере содержатся значения, а не указатели (например, QList<int>, а не QList<int *>). Если вы хотите удалить все элементы, хранящиеся в контейнере, используйте qDeleteAll().

Цитировать
Код
C++ (Qt)
QList<Employee *> list;
list.append(new Employee("Blackpool", "Stephen"));
list.append(new Employee("Twist", "Oliver"));
 
qDeleteAll(list.begin(), list.end());
list.clear();


Название: Re: Работа с контейнерами - терзают сомненья
Отправлено: SABROG от Январь 20, 2010, 21:04
Вместо хранения указателей в контейнерах вполне можно использовать QSharedPointer, тогда при вызове clear() указатели удаляться автоматически.


Название: Re: Работа с контейнерами - терзают сомненья
Отправлено: Igors от Январь 21, 2010, 21:30
и вообще можно ти делать такие функции, которые будут приниматть или возвращать контейнеры, не является ли это плохим стилем ?
Хотя делать такие ф-ции не запрещено, это является плохим стилем (и не стоит смотреть/уповать на то что Qt это частенько делает). Память будет освобождена (как обсуждалось выше) но ценой выполнения очень многих действий, в Вашем конкретном примере Вы погасили скорость выполнения в несколько раз без всякой необходимости. По классике это звучит примерно так:

Цитировать
Настоятельно рекомендуется передавать структуры по указателю/ссылке, хотя это и связано с потенциальными ошибками
 
Это совсем нетрудно и никак не диннее, например:
Код:
void addValue( QVector<int> & vec )
{
    vec << 10 << 11 << 12;
}


Название: Re: Работа с контейнерами - терзают сомненья
Отправлено: vanessa от Январь 21, 2010, 22:00

Настоятельно рекомендуется передавать структуры по указателю/ссылке, хотя это и связано с потенциальными ошибками
 
Это совсем нетрудно и никак не диннее, например:
Код:
void addValue( QVector<int> & vec )
{
    vec << 10 << 11 << 12;
}

[/quote]

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



Название: Re: Работа с контейнерами - терзают сомненья
Отправлено: BRE от Январь 21, 2010, 22:03
(и не стоит смотреть/уповать на то что Qt это частенько делает).
Разработчики Qt сначала потрудились и реализовали все классы-контейнеры с механизмом Implicit Sharing. Поэтому объекты этих классов можно спокойно возвращать из функций и никакого оверхеда это не вызовет.
Другое дело, что с любыми объектами так делать не всегда хорошо...

Настоятельно рекомендуется передавать структуры по указателю/ссылке, хотя это и связано с потенциальными ошибками
   
Кем рекомендуется?


Название: Re: Работа с контейнерами - терзают сомненья
Отправлено: Igors от Январь 22, 2010, 21:57
Я могу вернуть указатель  на данные, но не будет ли правильнее вернуть сами данные ?
Правильнее то как нужно в задаче  :) Возвращая "сами данные" Вы тем самым вызываете их копирование (и последующее удаление). А это совсем недешево, особенно для сложных данных. Если Вы делаете это намеренно/осознанно (да, здесь нужна копия) - на здоровье. А иначе надо использовать константные указатели и/или ссылки. 

Разработчики Qt сначала потрудились и реализовали все классы-контейнеры с механизмом Implicit Sharing.
Механизм Implicit Sharing (или shallow copy) встроен в конкретные классы, контейнеры сами по себе ничего не решают. Например:

QString умеет создавать "быстрые копии"
Код:
QVector <QString> vec;
..
QString s = vec[0];   // это не выделяет память (пока)
..
}  //  здесь вызовется  деструктор s который сработает быстро (если s не менялось)   

А std::string не умеет
Код:
QVector <std::string> vec;
..
std::string s = vec[0];   // выделение памяти и копирование данных (+ время)
..
}  //  деструктор s освобождает память (+время)
Т.е. тот же самый контейнер может давать очень разную производительность в зависимости от "начинки".

Поэтому объекты этих классов можно спокойно возвращать из функций и никакого оверхеда это не вызовет.
"Никакого" - это для языков типа Clipper, с которого, возможно, Вы начинали  :) Расходы все равно будут и для скоростных задач они все равно неприемлемы. Др. дело что они будут намного меньше по сравнению с "копированием в лоб" напр. как в STL. Здесь конечно, Qt смотрится посильнее.


Название: Re: Работа с контейнерами - терзают сомненья
Отправлено: BRE от Январь 22, 2010, 22:16
Возвращая "сами данные" Вы тем самым вызываете их копирование (и последующее удаление). А это совсем недешево, особенно для сложных данных.
Мы говорим о возвращении из функции Qt-контейнеров, поэтому никакого копирования при этом не происходит. Копирование будет выполнено только при попытке изменения данных в результирующем контейнере.

Механизм Implicit Sharing (или shallow copy) встроен в конкретные классы, контейнеры сами по себе ничего не решают. Например:
Данные контейнера хранятся в разделяемом-объекте, так же как и данные QString. Поэтому, при присвоении/возвращении/передаче в параметрах контейнера реально передается  только указатель на приватный объект.
Никакого дополнительного копирования данных не происходит, до момента их изменения.

QString умеет, std::string нет, поэтому я написал:
Цитировать
Другое дело, что с любыми объектами так делать не всегда хорошо...
Но, вопрос был про Qt-контейнеры, поэтому спокойно можно.

"Никакого" - это для языков типа Clipper, с которого, возможно, Вы начинали  :) Расходы все равно будут и для скоростных задач они все равно неприемлемы. Др. дело что они будут намного меньше по сравнению с "копированием в лоб" напр. как в STL. Здесь конечно, Qt смотрится посильнее.
Начинал я с ассемблера для 6502, Клипер обошел меня стороной.  :)
Расходы будут такие же, как возвращение ссылки/указателя.
Для скоростных задач самое важное оптимизация алгоритмов, а не попытки оптимизировать код реализующий медленный алгоритм, путем отказа от конструкторов/деструкторов и т.д.


Название: Re: Работа с контейнерами - терзают сомненья
Отправлено: vanessa от Январь 22, 2010, 22:18
А иначе надо использовать константные указатели и/или ссылки. 

тоесть  моя функция, если б она была членом класса была бы такая
Код:
const QVector<int>& getValue();
?



Название: Re: Работа с контейнерами - терзают сомненья
Отправлено: BRE от Январь 22, 2010, 22:25
тоесть  моя функция, если б она была членом класса была бы такая
Код:
const QVector<int>& getValue();
Так ты возвращаешь ссылку на локальный объект, который разрушиться при выходе из функции.
Для Qt-контейнеров, та функция, которую ты привел в первом посте, абсолютно корректная. Никакого копирования данных происходить не будет.
Лучше посмотреть самому, как реализованы контейнеры в Qt.


Название: Re: Работа с контейнерами - терзают сомненья
Отправлено: vanessa от Январь 22, 2010, 22:40
я вообще-то  имел ввиду примерно следующее
Код:
class blabla
{
public:
const QVector<int>& getValue()
{
return Value;
}

private:
QVector<int> Value;
}
если не углубятся в особенности реализации контейнеров Qt то пока будет существовать экземпляр подобного класса его функция getValue(); будет возвращать не сами данные а ссылку на них ?


Название: Re: Работа с контейнерами - терзают сомненья
Отправлено: BRE от Январь 22, 2010, 22:52
если не углубятся в особенности реализации контейнеров Qt то пока будет существовать экземпляр подобного класса его функция getValue(); будет возвращать не сами данные а ссылку на них ?
Для подобных случаев лучше возвращать константную ссылку.
Но, на самом деле, для Qt-контейнеров это не так важно. Если возвращать по значению, то копирования самих данных все равно происходить не будет.


Название: Re: Работа с контейнерами - терзают сомненья
Отправлено: Igors от Январь 23, 2010, 21:14
Но, на самом деле, для Qt-контейнеров это не так важно. Если возвращать по значению, то копирования самих данных все равно происходить не будет.
Давайте на примерах

Код:
struct Test {
 double a, b, c, d;
 char data[256];
};
QVector <Test> vec;
vec.resize(10);
Test t1 = vec[0];  //  (a, b, c, d, data) будут скопированы

Др. пример
Код:
struct Test {
 double a, b, c, d;
};
QVector <QString> vec;
vec.push_back("text");
QString s1 = vec[0]; 
Да, действительно, сама строка "text" копироваться не будет (пока s1 не изменена), не будет и выделения памяти при присваивании. Не будет и освобождения в деструкторе. Но ведь QString еще много чего имеет кроме самих данных ("text") - и все эти члены класса будут скопированы в s1. А это ну никак не скорость возврата по указателю/ссылке.

Ну и конечно, все это только до первой модификации s1.
Код:
s1 += "a";
И получаем все прелести копирования по значению - по полной программе.


Название: Re: Работа с контейнерами - терзают сомненья
Отправлено: BRE от Январь 23, 2010, 21:32
Давайте на примерах
Давай.
Речь идет о издержках при возвращении Qt-контейнеров по значению, т.е.:
Код
C++ (Qt)
struct Test
{
...
};
 
QVector<Test> buildVector()
{
QVector<Test> vec;
// Заполняем вектор
return vec;
}
 
void func()
{
QVector<Test> v = buildVector();
}
 

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

Вспомним, что вектор хранит данные в непрерывном блоке и может сообщить нам адрес его первого элемента.
Тогда меняем код и смотри:

Код
C++ (Qt)
struct Test
{
...
};
 
QVector<Test> buildVector()
{
QVector<Test> vec;
// Заполняем вектор
qDebug() << vec.data() << vec.size();
return vec;
}
 
void func()
{
QVector<Test> v = buildVector();
qDebug() << vec.data() << vec.size();
}
 

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

Код
C++ (Qt)
QVector<Test> vec;
qDebug << sizeof( vec );
 
Это к тем издержкам равным передаче ссылки/указателя...


Название: Re: Работа с контейнерами - терзают сомненья
Отправлено: Igors от Январь 24, 2010, 00:19
Код
C++ (Qt)
struct Test
{
...
};
 
QVector<Test> buildVector()
{
QVector<Test> vec;
// Заполняем вектор
return vec;
}
 
void func()
{
QVector<Test> v = buildVector();
}
 

Я утверждаю, что в этом коде не будут копироваться данные находящиеся в векторе и издержки при таком использовании будут такими же, как и при использовании ссылок (или указателей).
Я полностью согласен с Вашим утверждением, но это не имеет никакого отношения к Qt контейнерам, ни к Qt вообще. Все то же самое будет работать с std::vector или просто с любым классом (даже и не контейнером). Как объяснил Rcus такая оптимизация встроена практически во все современные компиляторы. Для строки

Код:
QVector<Test> v = buildVector();
выполнится один конструктор и ни одного деструктора QVector. Однако нет никаких оснований обобщать этот частный случай. Напр. попробуем присвоить еще раз

Цитировать
...
v = buildVector();
И "увы" - мы имеем вызванный деструктор (для возвращенного объекта) + оператор присваивания, который примерно = конструктор + деструктор


Название: Re: Работа с контейнерами - терзают сомненья
Отправлено: BRE от Январь 24, 2010, 00:26
Я полностью согласен с Вашим утверждением, но это не имеет никакого отношения к Qt контейнерам, ни к Qt вообще. Все то же самое будет работать с std::vector или просто с любым классом (даже и не контейнером). Как объяснил Rcus такая оптимизация встроена практически во все современные компиляторы.
Это имеет прямое отношение к Qt-контейнерам. Можно сделать еще двадцать присвоений и для всех векторов будет одна копия данных.
Это легко проверяется с помощью вызова метода: vec.isDetached(), который сообщает, эксклюзивно или нет используются данные. А использует он для этого атомарную переменную ref, содержащую число объектов QVector, которые разделяю данные.
Стоит все таки заглянуть в исходники Qt.

Насчет того-же самого с std::vector...
Не умеет std::vector такого, не умеет... Поэтому, я и написал о том, что Тролли вначале поработали, а теперь могут использовать...
Код
C++ (Qt)
#include <QApplication>          
#include <QVector>              
#include <vector>                
#include <QDebug>                
 
QVector<int> buildVector()
{                        
       QVector<int> vec;
       for( int i = 0; i < 100; ++i )
               vec.append( i );
 
       qDebug() << vec.constData() << vec.size();
       return vec;
}
 
std::vector<int> buildVector_std()
{
       std::vector<int> vec;
       for( int i = 0; i < 100; ++i )
               vec.push_back( i );
 
       qDebug() << &vec[ 0 ] << vec.size();
       return vec;
}
 
int main( int /*argc*/, char */*argv*/[] )
{
       QVector<int> vec = buildVector();
       qDebug() << vec.constData() << vec.size();
       QVector<int> vec_new = vec;
       qDebug() << vec_new.constData() << vec_new.size();
 
       qDebug() << "-------------------------------------------------";
 
       std::vector<int> vec1 = buildVector_std();
       qDebug() << &vec1[ 0 ] << vec1.size();
       std::vector<int> vec1_new = vec1;
       qDebug() << &vec1_new[ 0 ] << vec1_new.size();
 
       return 0;
}
 
Цитировать
0xc44fa0 100
0xc44fa0 100
0xc44fa0 100
-------------------------------------------------
0xc46cc0 100
0xc46cc0 100
0xc46ed0 100


И "увы" - мы имеем вызванный деструктор (для возвращенного объекта) + оператор присваивания, который примерно = конструктор + деструктор
А я и не говорил, что не будет вызван деструктор для локального объекта. Он вызовется, также как и оператор присваивания, только данные копироваться не будут. Скопируется только указатель на внутренний shared-объект.

Код
C++ (Qt)
inline ~QVector() { if (!d) return; if (!d->ref.deref()) free(p); }
А если вызов деструктора, который не делает никаких тяжелых операций, считать большими издержками, то для чего использовать C++?
На счет такой "оптимизации" я выше уже высказался.