Версия для печати темы

Нажмите сюда для просмотра этой темы в обычном формате

Форум на CrossPlatform.RU _ С\С++ _ как протестировать оператор присваивания и конструктор копирования класса?

Автор: Litkevich Yuriy 20.4.2010, 15:36

Собственно вопрос: как протестировать класс на корректность работы самописаных конструктора копирования и оператора присваивания, и на предмет их существования вообще.

Положим, что в конструкторе класса создаётся динамический объект, а в деструкторе удаляется.

Если свои конструктора копирования и оператора присваивания не определять, то компилятор будет использовать побитовое копирование.

При этом могут возникать коварные ситуации. Как я понимаю, связанные с двойным освобождением ресурсов.

Можно ли написать код, который бы осуществлял подобный тест?

Автор: Andrewshkovskii 20.4.2010, 15:52

http://www.devexp.ru/2010/03/o-kopirovanii-obektov-v-c/
вот тут кое-что интересное о копировании объектов и присваивании.

Автор: Влад 20.4.2010, 17:16

Цитата(Litkevich Yuriy @ 20.4.2010, 15:36) *
Если свои конструктора копирования и оператора присваивания не определять, то компилятор будет использовать побитовое копирование.

Не совсем так. Компилятор будет использовать почленное копирование. Для POD-типов он вполне может оптимизировать его до побитового, типа inline memcpy - например, MSVC++ именно так и делает.

Добавлено:
для тестирования корректности работы отдельных функций, к каковым как раз и относятся упомянутые, применяй юнит-тестирование.
Вот одна из (легких) библиотек для юнит-тестирования: UnitTest++, http://unittest-cpp.sourceforge.net/
Или посмотри на Boost.Test: http://www.boost.org/doc/libs/1_34_0/libs/test/doc/index.html

Автор: Litkevich Yuriy 20.4.2010, 17:41

я пока примитивный тест сделал (мой класс - простенький буфер):
1) проверяем правильность копирования (идентичность копии)
2) модифицируем исходный буфер, новый не должен поменяться
3) модифицируем новый буфер, исходный не должен поменяться

Если я закоментирую оператор присваивания и конструктор копирования, два последних теста не проходят (как и ожидалось).
Код весь находится в функции main и выглядит примерно так:

    // тестируем конструктор копирования
    {
        // инициализируем буфер масивом
        YBuffer1 buffer(rowBuffer, SIZE);
        YBuffer1 bufferCopy(buffer);
        // проверяем правильность копирования
        ok = true;
        for (i = 0; i < SIZE; i++){
            if (bufferCopy[i] != buffer.at(i)){
                ok = false;
            }
        }
        if (ok){
            qDebug() << "test COPY CONSTRUCTOR, coping - passed";
        }else{
            qDebug() << "test COPY CONSTRUCTOR, coping - ERROR";
        }
        const unsigned int index1 = SIZE/2;
        // модифицируем исходный, новый не должен поменяться
        char originalFirst = buffer.at(index1);
        buffer[index1] = ~buffer.at(index1);
        if (bufferCopy.at(index1) == originalFirst){
            qDebug() << "test COPY CONSTRUCTOR, modifying original - passed";
        }else{
            qDebug() << "test COPY CONSTRUCTOR, modifying original - ERROR";
        }        
        
        // модифицируем новый, исходный не должен поменяться
        const unsigned int index2 = index1/2;
        char originalSecond = buffer.at(index2);
        bufferCopy[index2] = ~bufferCopy.at(index2);
        if (buffer.at(index2) == originalSecond){
            qDebug() << "test COPY CONSTRUCTOR, modifying new - passed";
        }else{
            qDebug() << "test COPY CONSTRUCTOR, modifying new - ERROR";
        }        
    }// конец теста    
...
//ещё несколько тестов
Т.е. я ограничил область видимости каждого теста фигурными скобками, и полагал, что по выходу за эти скобки, программа должна аварийно завершатся при отсутствии собственных конструктора копирования и оператора присваивания. Но программа падает почему-то после всех тестов, а не когда произойдёт выход за фигурные скобки.

Автор: Влад 20.4.2010, 18:06

Гмм.... А можно взглянуть на код этого YBuffer1?

Автор: DIMEDROLL 20.4.2010, 18:30

Цитата(Litkevich Yuriy @ 20.4.2010, 17:41) *
я пока примитивный тест сделал (мой класс - простенький буфер):

я бы поступил так:
- Для классов которые не должны копироваться использую http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showone=Copy_Constructors#Copy_Constructors
- Для копируемых темплейтная фукция:
template<typename T>
void TestCopyAndAssign(const T & obj) {
  T copy(obj);
  assert((copy == obj) && "Testing copy failed");
  T assign;
  assign = obj;
  assert((assign == obj) && "Testing assign failed");
}

Весь код проверки находится в операторе сравнения(==). Для каждого класса не надо писать свою функцию теста, просто реализовать ==.
В реализации == обязательно проверять указатели на равенство. Например
class A {
public:
  B *b;
  //... all operators
  bool operator==(const A &obj) {
    assert((this->b != obj.b) && "Shit happened, object copying or assigning doesn't handle pointers");
    return *this->b == *obj.b;
  }
}

Автор: Litkevich Yuriy 20.4.2010, 18:52

Цитата(Влад @ 20.4.2010, 22:06) *
Гмм.... А можно взглянуть на код этого YBuffer1?
можно, заодно может на косяки укажите:
ybuffer1.h
#ifndef YBUFFER1
#define YBUFFER1

/**    Класс YBuffer1 является ... .
*     Этот класс ...
*/
class YBuffer1
{

public:
    YBuffer1(const char *abuffer, unsigned int asize);
    ~YBuffer1();
    YBuffer1(const YBuffer1 &other);
    YBuffer1 &operator=(const YBuffer1 &other);
    
    const char& at(unsigned int i) const;
    char& operator[](unsigned int i);
    char operator[](unsigned int i) const;
    
    unsigned int size() const {return theSize;}

private:
    unsigned int theSize;
    char *theBuffer;

private:
    void copy(const YBuffer1 &source, YBuffer1 &destination);

};

#endif //YBUFFER1

ybuffer1.cpp
#include "ybuffer1.h"

#include <QtGlobal>


YBuffer1::YBuffer1(const char *abuffer, unsigned int asize)
{
    theSize = asize;
    theBuffer = new char[asize];
    // тут надо скопировать содержимое
    unsigned int cnt = 0;
    while(cnt != asize){
        theBuffer[cnt] = abuffer[cnt];
        cnt++;
    }
}

YBuffer1::~YBuffer1()
{
    delete theBuffer;
}

YBuffer1::YBuffer1(const YBuffer1 &other)
{
    theSize = other.size();
    theBuffer = new char[other.size()];
    // тут надо скопировать содержимое
    copy(other, *this);
}

YBuffer1& YBuffer1::operator=(const YBuffer1 &other)
{
    theSize = other.size();
    theBuffer = new char[other.size()];
    // тут надо скопировать содержимое
    copy(other, *this);
    return *this;
}

void YBuffer1::copy(const YBuffer1 &source, YBuffer1 &destination)
{
    unsigned int size = source.size();
    Q_ASSERT_X(size == destination.size(), "YBuffer1::copy()", "size of objects not eqal");
    unsigned int cnt = 0;
    while(cnt != size){
        destination[cnt] = source[cnt];
        cnt++;
    }
}

const char& YBuffer1::at(unsigned int i) const
{
    Q_ASSERT_X(i <= theSize, "YBuffer1::at()", "requested index too big");
    const char &data = theBuffer[i];
    return data;
}

char& YBuffer1::operator[](unsigned int i)
{
    Q_ASSERT_X(i <= theSize, "YBuffer1::operator[]", "requested index too big");
    char &data = theBuffer[i];
    return data;
}

char YBuffer1::operator[](unsigned int i) const
{
    Q_ASSERT_X(i <= theSize, "YBuffer1::operator[]", "requested index too big");
    char data = theBuffer[i];
    return data;
}


Цитата(DIMEDROLL @ 20.4.2010, 22:30) *
Для копируемых темплейтная фукция:
эта шаблонная функция ничего не даст, в отсутствии самописанных конструктора копирования и оператора присваивания

Цитата(DIMEDROLL @ 20.4.2010, 22:30) *
Весь код проверки находится в операторе сравнения(==).
про это не понял, где и как применяется

Автор: DIMEDROLL 20.4.2010, 18:59


Цитата
Цитата(DIMEDROLL @ 20.4.2010, 22:30) *
Для копируемых темплейтная фукция:
эта шаблонная функция ничего не даст, в отсутствии самописанных конструктора копирования и оператора присваивания

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

Цитата
Цитата(DIMEDROLL @ 20.4.2010, 22:30) *
Весь код проверки находится в операторе сравнения(==).
про это не понял, где и как применяется


Я имел ввиду: если operator== работает правильно, то его можно использовать в моей шаблонной функции для проверки копирования и присваивания

Автор: Litkevich Yuriy 20.4.2010, 19:03

Цитата(DIMEDROLL @ 20.4.2010, 22:59) *
не пишет реализацию конструктора копирования и оператора присваивания, то он подразумевает что по вариант по умолчанию его
собственно, я рассматриваю, отсутствие, как частный (а может и вовсе жёсткий) случай неправильной реализации КК и ОП. Т.е. должен быть какой-то чёткий эффект если что-то сделано не верно.

Автор: DIMEDROLL 20.4.2010, 19:07

Цитата(Litkevich Yuriy @ 20.4.2010, 19:03) *
собственно, я рассматриваю, отсутствие, как частный (а может и вовсе жёсткий) случай неправильной реализации КК и ОП. Т.е. должен быть какой-то чёткий эффект если что-то сделано не верно.

что такое КК и ОП?
Цитата
Т.е. должен быть какой-то чёткий эффект если что-то сделано не верно.

Что-то это что?
чёткий эффект это какой?

Автор: Влад 20.4.2010, 19:12

Я немного подправил код.... Правда, не компилировал! Только то, что бросается в глаза при чтении.

Раскрывающийся текст
#include "ybuffer1.h"

#include <QtGlobal>


YBuffer1::YBuffer1(const char *abuffer, unsigned int asize)
{
    theSize = asize;
    theBuffer = new char[asize];
    // тут надо скопировать содержимое
    // поскольку abuffer - POD, то можно не делать цикл, а тупо использовать memcpy
    // или использовать std::copy
    memcpy(theBuffer, abuffer, asize);
    // хотя можно использовать и цикл... дело вкуса, но практически всегда memcpy выполняется
    // быстрее.
/*    
    unsigned int cnt = 0;
    while(cnt != asize){
        theBuffer[cnt] = abuffer[cnt];
        cnt++;
    }
*/
}

YBuffer1::~YBuffer1()
{
    // эта ошибка приводит к UB! Стандарт, 5.3.5/2
    /* delete theBuffer; */
    delete[] theBuffer;
}

YBuffer1::YBuffer1(const YBuffer1 &other)
{
    theSize = other.size();
    theBuffer = new char[other.size()];
    // тут надо скопировать содержимое
    copy(other, *this);
}

YBuffer1& YBuffer1::operator=(const YBuffer1 &other)
{
    // 1. проверить присваивание самому себе, типа i = i;
    if (this != &other)
    {
        // 2. перед выделением нового буфера надо удалить старый.
        // Еще лучше - использовать технику, безопасную по отношению к исключениям,
        // см. Саттер, "Решение сложных задач на C++"
        delete[] theBuffer;
        
        theBuffer = new char[other.size()];
        // тут надо скопировать содержимое
        copy(other, *this);
        theSize = other.size();
    }
    return *this;
}

void YBuffer1::copy(const YBuffer1 &source, YBuffer1 &destination)
{
    unsigned int size = source.size();
    Q_ASSERT_X(size == destination.size(), "YBuffer1::copy()", "size of objects not eqal");
    memcpy(destination.theBuffer, source.theBuffer, source.size());
/*    
    unsigned int cnt = 0;
    while(cnt != size){
        destination[cnt] = source[cnt];
        cnt++;
    }
*/
}

// здесь везде i должны быть строго < theSize!
    
const char& YBuffer1::at(unsigned int i) const
{
    Q_ASSERT_X(i < theSize, "YBuffer1::at()", "requested index too big");
    return theBuffer[i];
}

char& YBuffer1::operator[](unsigned int i)
{
    Q_ASSERT_X(i < theSize, "YBuffer1::operator[]", "requested index too big");
    return theBuffer[i];
}

char YBuffer1::operator[](unsigned int i) const
{
    Q_ASSERT_X(i < theSize, "YBuffer1::operator[]", "requested index too big");
    return theBuffer[i];
}

Автор: Litkevich Yuriy 20.4.2010, 19:16

Цитата(DIMEDROLL @ 20.4.2010, 23:07) *
что такое КК и ОП?
Конструктор Копирования и Оператор Присваивания

Цитата(Влад @ 20.4.2010, 23:12) *
поскольку abuffer - POD
что означает эта абревиатура?

Цитата(DIMEDROLL @ 20.4.2010, 23:07) *
Что-то это что?
чёткий эффект это какой?
Что-то - некоректный КК и/или ОП
чёткий, например сообщение: "Конструктор копирования работает не корректно"

Автор: Влад 20.4.2010, 19:19

POD - Plain Old Data.

Автор: Litkevich Yuriy 20.4.2010, 19:23

Цитата(Влад @ 20.4.2010, 23:12) *
// эта ошибка приводит к UB! Стандарт, 5.3.5/2
/* delete theBuffer; */
delete[] theBuffer;
вот про это я не понял, а что тут неопределённого?

Цитата(Влад @ 20.4.2010, 23:12) *
// здесь везде i должны быть строго < theSize!
О-о-о, надо срочником тест подправить, а то выход за пределы индексации я не проверял. Спасибо!

Цитата(Влад @ 20.4.2010, 23:19) *
Plain Old Data
это мне тоже ни о чём не говорит, "Плоские старые данные" :unknw:

Автор: DIMEDROLL 20.4.2010, 19:34

Цитата
Конструктор Копирования и Оператор Присваивания
Что-то - некоректный КК и/или ОП
чёткий, например сообщение: "Конструктор копирования работает не корректно"

В таком случае мой код(функция и оператор==) соотвествует данном описанию, если что не так - будет ассерт с сообщением.
POD это int, double, short .... тоесть встроенные типы Си
P.S гуглится за меньше минуту ;)
Добавлено: был неправ, POD - структура данных не имеющая никаких методов, только члены-переменные
http://en.wikipedia.org/wiki/Plain_Old_Data_Structures

Автор: Влад 20.4.2010, 21:36

Юрий, просто Стандарт языка (п. 5.3.5 положение (clause) 2) четко описывает требования к выделению и удалению памяти:
- если память была выделена по new для единичного объекта (неважно, какого типа) - то удаляться она должна только delete для единичного объекта;
- если память была выделена по new[] для массива объектов (array form) - то удаляться она должна только delete[] для массива объектов (тоже array form) и никак иначе.
Иначе поведение программы не определено (Otherwise behavior is undefined, если мой склероз мне не изменяет). Причем, Стандарт никак не конкретизирует это "неопределенное поведение" - программе разрешается делать все, что ей заблагорассудится, начиная от порчи памяти и невоспроизводимых глюков, и вплоть до format C:.

Автор: Litkevich Yuriy 21.4.2010, 12:57

Цитата(Влад @ 20.4.2010, 23:12) *
см. Саттер, "Решение сложных задач на C++"
тут обнаружил дома книжку: "Герб Саттер и Андрей Александреску. Стандарты программирования на С++. 101 правило и рекомендация." :)

Автор: AD 21.4.2010, 17:48

Цитата(Litkevich Yuriy @ 21.4.2010, 13:57) *
тут обнаружил дома книжку: "Герб Саттер и Андрей Александреску. Стандарты программирования на С++. 101 правило и рекомендация." :)

Круто. Как раз эту книжку давно хотел купить, но она неожиданно исчезла с прилавков магазинов! Почитай. Вещь, уверен, что полезная. Отдельные книги Саттера и Александреску я читал! :)

Форум Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)