Форум программистов
 

Восстановите пароль или Зарегистрируйтесь на форуме, о проблемах и с заказом рекламы пишите сюда - alarforum@yandex.ru, проверяйте папку спам!

Вернуться   Форум программистов > C/C++ программирование > Общие вопросы C/C++
Регистрация

Восстановить пароль
Повторная активизация e-mail

Купить рекламу на форуме - 42 тыс руб за месяц

Ответ
 
Опции темы Поиск в этой теме
Старый 29.08.2021, 22:02   #1
SourceOfDeath
Новичок
Джуниор
 
Регистрация: 29.08.2021
Сообщений: 3
По умолчанию Как правильно перегрузить swap для моего класса?

Здравствуйте.
Читаю книгу "C++ Primer. 5 edition". Нужно выполнить упражнение - перегрузить функцию swap() для своего класса. Для этого предлагается объявить её в том-же пространстве имён, что и сам класс. Я так и сделал. При вызове swap() действительно вызывается перегруженая функция, а при вызове std::swap() - соответственно, std::swap(). Но при вызове std::sort() обмены осуществляются только с помощью std::swap(), что неприемлемо - должна вызываться специальная функция.
Нашёл готовый код упражнений к книге:
https://github.com/Mooophy/Cpp-Prime...ch13/ex13_31.h
https://github.com/Mooophy/Cpp-Prime...13/ex13_31.cpp
Пишу в Visual Studio Community 2019.
Архив с проектом: vopros_swap.rar.
Там есть нюанс: в одной папке лежат файлы моего класса HasPtr (HasPtr.h, HasPtr.cpp) и скачанные по ссылке (ex13_31.h, ex13_31.cpp). Я исключаю ненужные файлы из проекта (не удаляю), а нужные - включаю. И так переключаюсь между своим кодом и скачанным. В скачанный код добавил вывод отладочных сообщений.
Заранее благодарен.
SourceOfDeath вне форума Ответить с цитированием
Старый 31.08.2021, 07:38   #2
Алексей1153
фрилансер
Форумчанин
 
Регистрация: 11.10.2019
Сообщений: 960
По умолчанию

SourceOfDeath, вот, пробую два файла по ссылке:
https://onlinegdb.com/WGQdbMj7r

вывод
Код:
call swap(HasPtr &rhs)
call swap(HasPtr &rhs)
call swap(HasPtr &rhs)
call swap(HasPtr &rhs)
a
c
s
так, вроде, всё вызывается, что нужно

а почему в операторе < не проверяется ps на nullptr?

если ответ "он всегда заполнен", то следует вопрос - а зачем ps вообще указатель? Почему не поле? Ведь это многое упрощает
https://onlinegdb.com/olLSsG-W9

Последний раз редактировалось Алексей1153; 31.08.2021 в 07:47.
Алексей1153 вне форума Ответить с цитированием
Старый 31.08.2021, 21:07   #3
SourceOfDeath
Новичок
Джуниор
 
Регистрация: 29.08.2021
Сообщений: 3
По умолчанию

Спасибо, Алексей1153, что попытались помочь.
Цитата:
так, вроде, всё вызывается, что нужно
На самом деле - нет. Mooophy запутал следы. Отладочное сообщение "call swap(HasPtr &rhs)" вызывается в закрытом методе swap, а этот метод вызывается в операторе присваивания и в функции swap. Я скопировал код из вашей первой ссылки, перенёс вывод отладочного сообщения в функцию swap из метода - и это сразу расставило точки над Ё. Функция не вызывается. При запуске проекта из архива по отладочным сообщениям видно, как во время сортировки многократно:
-вызывается копирующий конструктор
-вызывается два раза оператор присваивания
-вызывается деструктор.
Это свидетельствует, что в std::sort вызывается std::swap, а не перегруженная специальная функция.
Наверно, я затупил, что не вставил в пост с вопросом исходный код.
тестовая программа:
Код:
#include <iostream>
using std::cout; 
using std::cin;
using std::endl;

#include <vector>
using std::vector;
#include <string>
using std::string;

#include<algorithm>

#include"HasPtr.h"
//#include"ex13_31.h"

int main(int argc, char *argv[]) 
{
	cout << "start" << endl;
	
	vector<HasPtr> v;
	v.reserve(10);
	v.emplace_back("one");
	v.emplace_back("two");
	v.emplace_back("three");
	v.emplace_back("four");
	v.emplace_back("five");
	v.emplace_back("six");
	v.emplace_back("seven");
	v.emplace_back("eight");
	v.emplace_back("nine");
	v.emplace_back("ten");

	//test swap
	cout << "\n  swap  \n";
	swap(v[0], v[1]);
	cout << "\n  std::swap  \n";
	std::swap(v[0], v[1]);
	//test std::sort
	cout << endl << "\n  Sort Start  \n";
	//using std::swap;
	std::sort(v.begin(), v.end());
	cout << "\n  Sort Ends  \n";
	
   	cout << "\n  end  \n";
	return EXIT_SUCCESS;
}
HasPtr.h:
Код:
#pragma once
#include<string>
using std::string;

#include<iostream>

class HasPtr
{
public:
	friend void swap(HasPtr& lhs, HasPtr& rhs);
	friend bool operator<(const HasPtr&, const HasPtr&);

	HasPtr(const string& s = string()) : 
		ps(new string(s)), i(0), 
		use(new size_t(1)) 
	{ 
		std::cout << "Construct new HasPtr " << s << std::endl; 
	}
		
	HasPtr(const HasPtr& original) : 
		ps(new string(*original.ps)), i(original.i), 
		use(original.use) 
	{ 
		++(*use); 
		std::cout << "Construct copy HasPtr with " << *ps << std::endl; 
	}

	~HasPtr();

	HasPtr& operator=(const HasPtr&);
private:
	string* ps;
	int i;
	size_t* use;
};

void swap(HasPtr& lhs, HasPtr& rhs);

bool operator<(const HasPtr&, const HasPtr&);
HasPtr.cpp:
Код:
#include "HasPtr.h"

HasPtr::~HasPtr()
{
    std::cout << "Destruct HasPtr with " << *ps << std::endl;
    --(*use);
    if (use == 0) 
    {
        delete ps;
        delete use;
    }
}

HasPtr& HasPtr::operator=(const HasPtr& other)
{
    std::cout << *ps << " = " << *other.ps << std::endl;

    ++(*other.use);
    --(*use);

    if (use == 0)
    {
        delete ps;
        delete use;
    }

    ps = other.ps;
    i = other.i;
    use = other.use;
    return *this;
}

void swap(HasPtr& lhs, HasPtr& rhs)
{
    std::cout << "swap " << *lhs.ps << " with " << *rhs.ps << std::endl;
    //using std::swap;
    swap(lhs.ps, rhs.ps);
    std::swap(lhs.i, rhs.i);
    std::swap(lhs.use, rhs.use);
}

bool operator<(const HasPtr& lhs, const HasPtr& rhs)
{
    return *lhs.ps < *rhs.ps;
}
мой вывод:
Цитата:
start
Construct new HasPtr one
Construct new HasPtr two
Construct new HasPtr three
Construct new HasPtr four
Construct new HasPtr five
Construct new HasPtr six
Construct new HasPtr seven
Construct new HasPtr eight
Construct new HasPtr nine
Construct new HasPtr ten

swap
swap one with two

std::swap
Construct copy HasPtr with two
two = one
one = two
Destruct HasPtr with two


Sort Start
Construct copy HasPtr with two
two = two
Destruct HasPtr with two
Construct copy HasPtr with three
three = two
two = three
Destruct HasPtr with three
Construct copy HasPtr with four
four = two
two = three
three = one
one = four
Destruct HasPtr with four
Construct copy HasPtr with five
five = two
two = three
three = one
one = four
four = five
Destruct HasPtr with five
Construct copy HasPtr with six
six = two
two = three
three = six
Destruct HasPtr with six
Construct copy HasPtr with seven
seven = two
two = three
three = six
six = seven
Destruct HasPtr with seven
Construct copy HasPtr with eight
eight = two
two = three
three = six
six = seven
seven = one
one = four
four = five
five = eight
Destruct HasPtr with eight
Construct copy HasPtr with nine
nine = two
two = three
three = six
six = seven
seven = one
one = nine
Destruct HasPtr with nine
Construct copy HasPtr with ten
ten = two
two = three
three = ten
Destruct HasPtr with ten

Sort Ends

end
Destruct HasPtr with eight
Destruct HasPtr with five
Destruct HasPtr with four
Destruct HasPtr with nine
Destruct HasPtr with one
Destruct HasPtr with seven
Destruct HasPtr with six
Destruct HasPtr with ten
Destruct HasPtr with three
Destruct HasPtr with two
Цитата:
а почему в операторе < не проверяется ps на nullptr?
Потому, что я ещё учусь. Это что-то типа самодельного счётчика ссылок по условиям упражниния (изначально). Если use > 0 то ссылка есть.
Цитата:
если ответ "он всегда заполнен", то следует вопрос - а зачем ps вообще указатель? Почему не поле? Ведь это многое упрощает
Сначала класс должен был вести себя как указатель (при копировании копировался указатель на строку), а затем его нужно было переделать, чтобы он вёл себя как заначение - независимые копии строки в каждом экземпляре. Это-же учебный проект, не стоит искать в нём глубокого смысла.

Последний раз редактировалось SourceOfDeath; 31.08.2021 в 21:21.
SourceOfDeath вне форума Ответить с цитированием
Старый 31.08.2021, 21:53   #4
Алексей1153
фрилансер
Форумчанин
 
Регистрация: 11.10.2019
Сообщений: 960
По умолчанию

SourceOfDeath, я попробовал, и вижу, что в std::sort не использует std::swap, он там, похоже, использует оператор и конструктор перемещения, которые у тебя не определены явно. А должны бы.

Подтверждение подозрения: если в файле HasPtr.h открыть строки 14, 15 или сразу обе, то код перестаёт компилиться (код по ссылке можно fork и править)
https://onlinegdb.com/I226nVguv

также у тебя забыт конструктор по умолчанию (я добавил)

Есть такое правило пяти

Цитата:
Сообщение от SourceOfDeath Посмотреть сообщение
Это-же учебный проект, не стоит искать в нём глубокого смысла
дело тут не в учебности. Оператор перемещения, созданный компилятором по умолчанию, неправильно работает с полями - указателями. Из-за их кривого содержимого запросто возникнет неопределённое поведение - весь эксперимент будет неправильный

к счастью, на практике такую кашу нет нужды делать, поскольку есть умные указатели и контейнеры

Последний раз редактировалось BDA; 31.08.2021 в 22:16.
Алексей1153 вне форума Ответить с цитированием
Старый 02.09.2021, 21:15   #5
SourceOfDeath
Новичок
Джуниор
 
Регистрация: 29.08.2021
Сообщений: 3
По умолчанию

Спасибо, Алексей1153.
Цитата:
которые у тебя не определены явно
. По всей видимости, это и есть ответ.
Эти разделы есть в книге, но они позже.
Цитата:
также у тебя забыт конструктор по умолчанию (я добавил)
Я думал, что конструктор с аргументами по умолчанию может быть использован по умолчанию. Я пробовал создавать объект как
Код:
HasPtr h();
v.emplace_back();
и вроде, работало.
Наверно, хорошая практика - объявлять сразу все 5 методов как =deleted, и по мере осознанной надобности и требований компилятора, их реализовывать.
У меня, получается, была возможность убедится в том, что std::sort не использует swap: нужно было поставить точку останова.
Спасибо за потраченное на меня время. Надеюсь, не зря. Большую часть книги уже усвоил.
SourceOfDeath вне форума Ответить с цитированием
Старый 03.09.2021, 07:39   #6
Алексей1153
фрилансер
Форумчанин
 
Регистрация: 11.10.2019
Сообщений: 960
По умолчанию

Цитата:
Сообщение от SourceOfDeath Посмотреть сообщение
Наверно, хорошая практика - объявлять сразу все 5 методов как =deleted
нет, это так себе практика. Функции по умолчанию - более эффективны (конструкторы, оператор =, деструктор)

хорошая практика - это правило ноля (упоминание есть по той же ссылке, где про правило пяти)
Цитата:
Правило ноля
Мартин Фернандес предложил также правило ноля.[5] По этому правилу не стоит определять ни одну из пяти функций самому; надо поручить их создание компилятору(присвоить им значение = default. Для владения ресурсами вместо простых указателей стоит использовать специальные классы-обёртки, такие как std::unique_ptr и std::shared_ptr.[6]
( std::unique_ptr - его можно переместить, но нельзя скопировать. std::shared_ptr - поддаётся копированию, поддерживает счётчик ссылок. На него можно сослаться без счётчика ссылок при помощи std::weak_ptr)

и мне очень часто удаётся это правило использовать То есть, я практически не пишу конструкторы, операторы = и деструкторы, но если один из них потребовался, то все остальные явно упоминаю с =default

очень редко бывает, что какую-нибудь из функций требуется удалить =delete
Алексей1153 вне форума Ответить с цитированием
Ответ


Купить рекламу на форуме - 42 тыс руб за месяц



Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Шаблонная friend функция swap шаблонного класса Aoizora Общие вопросы C/C++ 3 19.05.2017 17:14
Как правильно перегрузить << cout ? Jugger Помощь студентам 1 13.03.2013 00:40
Как правильно перегрузить логические операции? julia9311 Общие вопросы C/C++ 8 15.01.2013 13:44
Есть код,как правильно перегрузить конструктор -ushёl- Общие вопросы C/C++ 9 08.07.2010 10:32
Как мне из моего класса вывести сообщение? Utkin Общие вопросы Delphi 9 19.11.2009 14:43