Форум программистов, компьютерный форум, киберфорум
С++ для начинающих
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.56/9: Рейтинг темы: голосов - 9, средняя оценка - 4.56
495 / 209 / 70
Регистрация: 27.05.2016
Сообщений: 557

Разработать безопасный класс с идиомой pimpl

25.07.2016, 16:45. Показов 1953. Ответов 15
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Почему этот код у меня не компилируеться? И правильный ли выбран подход для решения обеспечения безопасности исключений?
widget.h
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#ifndef WIDGET_H
#define WIDGET_H
 
#include <memory>
#include <utility>
#include "widget_impl.h"
 
template <typename T, typename U>
class Widget
{
    template <typename, typename>
    class Widget_impl;
 
    std::unique_ptr<Widget_impl<T, U>> impl = nullptr;
 
public:
    Widget() = default;
 
    Widget(T&& t, U&& u) :
        impl(std::make_unique<Widget_impl<T, U>>(std::forward<T>(t),
                              std::forward<U>(u)))
    {}
 
    Widget(const Widget& other) :
        impl(other.impl ? std::make_unique<Widget_impl<T, U>>(*other.impl)
                          : nullptr)
    {}
 
    Widget& operator = (Widget other)
    {
        swap(other);
        return *this;
    }
 
    void swap(Widget& other) noexcept
    {
        std::swap(impl, other.impl);
    }
};
 
template <typename T, typename U>
Widget<T, U> makeWidget(T&& t, U&& u)
{
    return Widget<T, U>(std::forward<T>(t),
                  std::forward<U>(u));
}
 
#endif // WIDGET_H
widget_impl.h
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
#ifndef WIDGET_IMPL_H
#define WIDGET_IMPL_H
 
#include "widget.h"
 
template <typename T, typename U>
class Widget_impl
{
    T t;
    U u;
};
 
#endif // WIDGET_IMPL_H
main
C++
1
2
3
4
5
6
7
8
#include "widget.h"
 
int main()
{
    std::vector<int> v{1,2};
    std::string s = "hello";
    auto wg = makeWidget(v, s);
}
Ошибка: incomplete type для Widget. Но Widget то весь уже определен.
0
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
25.07.2016, 16:45
Ответы с готовыми решениями:

Можно ли создать безопасный многопоточный класс
Всем еще раз привет. Пишу класс, который общается с USB- устройством. У него есть методы, запускающие потоки инициализации, запуска...

Класс: Разработать абстрактный класс класс Point для задания координаты...
Всем привет, помогите пожалуйста решить задачу, я уже всю голову сломал, не знаю как решить... Разработать абстрактный класс класс...

Не безопасный код, почему он не безопасный.
Почему использование указателей в с# считается небезопасным?

15
Модератор
Эксперт С++
 Аватар для zss
13773 / 10966 / 6491
Регистрация: 18.12.2011
Сообщений: 29,244
25.07.2016, 17:26
Цитата Сообщение от notAll Посмотреть сообщение
#include "widget.h"
Удалить из widget_impl
0
495 / 209 / 70
Регистрация: 27.05.2016
Сообщений: 557
25.07.2016, 17:43  [ТС]
Цитата Сообщение от zss Посмотреть сообщение
Удалить из widget_impl
Да нет, это проблемы не решило.
0
19500 / 10105 / 2461
Регистрация: 30.01.2014
Сообщений: 17,817
25.07.2016, 17:57
Лучший ответ Сообщение было отмечено notAll как решение

Решение

Цитата Сообщение от notAll Посмотреть сообщение
Ошибка: incomplete type для Widget. Но Widget то весь уже определен.
У тебя определен Widget_impl, а Widget<T,U>::Widget_impl не определен.
Переусложнил, кстати, сильно. Зачем Widget_impl тоже делать шаблоном?

Добавлено через 3 минуты
notAll,
Это кстати не все еще. Почему у Widget_impl нет конструктора с аргументами? При этом создаем объект с инициализацией двумя параметрами. В общем вот:
widget.h

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#ifndef WIDGET_H
#define WIDGET_H
 
#include <memory>
#include <utility>
 
template <typename T, typename U>
class Widget
{
    class Widget_impl;
 
    std::unique_ptr<Widget_impl> impl = nullptr;
 
public:
    Widget() = default;
 
    Widget(T&& t, U&& u) :
        impl(std::make_unique<Widget_impl>(std::forward<T>(t), std::forward<U>(u)))
    {}
 
    Widget(const Widget& other) :
        impl(other.impl ? std::make_unique<Widget_impl>(*other.impl) : nullptr)
    {}
 
    Widget& operator=(Widget other)
    {
        swap(other);
        return *this;
    }
 
    void swap(Widget& other) noexcept
    {
        std::swap(impl, other.impl);
    }
};
 
#include "widget_impl.h"
 
template <typename T, typename U>
Widget<T, U> makeWidget(T&& t, U&& u)
{
    return Widget<T, U>(std::forward<T>(t), std::forward<U>(u));
}
 
#endif // WIDGET_H

widget_impl.h
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef WIDGET_IMPL_H
#define WIDGET_IMPL_H
 
template <typename T, typename U>
class Widget<T, U>::Widget_impl
{
    T t;
    U u;
public:
    Widget_impl(T && t, U && u)
        : t(std::forward<T>(t)), u(std::forward<U>(u))
    {}
};
 
#endif // WIDGET_IMPL_H
1
Эксперт С++
 Аватар для hoggy
8973 / 4319 / 960
Регистрация: 15.11.2014
Сообщений: 9,760
25.07.2016, 18:13
Лучший ответ Сообщение было отмечено notAll как решение

Решение

Цитата Сообщение от notAll Посмотреть сообщение
И правильный ли выбран подход для решения обеспечения безопасности исключений?
норм.


Цитата Сообщение от DrOffset Посмотреть сообщение
В общем вот:
вынуждает клиентов подключать не только Widget,
но и Widget_impl

что противоречит самой идеоме impl.
1
19500 / 10105 / 2461
Регистрация: 30.01.2014
Сообщений: 17,817
25.07.2016, 18:27
Цитата Сообщение от hoggy Посмотреть сообщение
вынуждает клиентов подключать не только Widget,
но и Widget_impl
что противоречит самой идеоме impl.
Заметьте, я лишь исправил ошибки компиляции. В исходном варианте ТС был все тот же шаблонный класс, который также рушит принцип. Не надо, пожалуйста, выставлять все так, будто я это предложил
0
Эксперт С++
 Аватар для hoggy
8973 / 4319 / 960
Регистрация: 15.11.2014
Сообщений: 9,760
25.07.2016, 18:52
Цитата Сообщение от DrOffset Посмотреть сообщение
Не надо, пожалуйста, выставлять все так, будто я это предложил
да не, я понял.
просто обращаю внимание.
не столько ваше, сколько ТС
0
495 / 209 / 70
Регистрация: 27.05.2016
Сообщений: 557
25.07.2016, 20:21  [ТС]
Цитата Сообщение от hoggy Посмотреть сообщение
вынуждает клиентов подключать не только Widget,
но и Widget_impl
Ну, тогда вот, я вынес реализацию в отдельный файл, но если перенести заголовок widget_impl.h в файл реализации, то возникают ошибки компиляции с фу-цией makeWidget.
widget.h
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#ifndef WIDGET_H
#define WIDGET_H
 
#include <memory>
#include <utility>
 
template <typename T, typename U>
class Widget
{
    class Widget_impl;
 
    std::unique_ptr<Widget_impl> impl = nullptr;
 
public:
    Widget() = default;
 
    //Widget(const T& t, const U& u);
 
    Widget(T&& t, U&& u);
 
    Widget(const Widget& other);
 
    Widget& operator=(Widget other);
 
    void swap(Widget& other) noexcept
    {
        std::swap(impl, other.impl);
    }
};
 
template <typename T, typename U>
Widget<T, U> makeWidget(T&& t, U&& u)
{
    return Widget<T, U>(std::forward<T>(t), std::forward<U>(u));
}
 
#endif // WIDGET_H
widget.cpp
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "widget.h"
#include "widget_impl.h"
 
template <typename T, typename U>
Widget<T, U>::Widget(T&& t, U&& u) :
impl(std::make_unique<Widget_impl>(std::forward<T>(t), std::forward<U>(u)))
{}
 
//template <typename T, typename U>
//Widget<T, U>::Widget(const T& t, const U& u) :
//        impl(std::make_unique<Widget_impl>(t, u))
//{}
 
template <typename T, typename U>
Widget<T, U>::Widget(const Widget& other) :
        impl(other.impl ? std::make_unique<Widget_impl>(*other.impl) : nullptr)
{}
 
template <typename T, typename U>
Widget<T, U>& Widget<T, U>::operator=(Widget other)
{
    swap(other);
    return *this;
}
widget_impl.h
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef WIDGET_IMPL_H
#define WIDGET_IMPL_H
 
template <typename T, typename U>
class Widget<T, U>::Widget_impl
{
    T t;
    U u;
public:
    Widget_impl(T&& t, U&& u)
        : t(std::forward<T>(t)), u(std::forward<U>(u))
    {}
};
 
#endif // WIDGET_IMPL_H
main
C++
1
2
3
4
5
6
7
8
9
10
11
12
#include "widget.h"
 
int main()
{
    auto wg = makeWidget(std::vector<int>{1,2}, std::string("hello"));
 
    std::vector<int> v{1,2};
    std::string s("hello");
    auto wg2 = makeWidget(v, s);
 
    //Widget<std::vector<int>, std::string> wg3(v, s); // тут похоже тоже проблема
}
0
Эксперт С++
 Аватар для hoggy
8973 / 4319 / 960
Регистрация: 15.11.2014
Сообщений: 9,760
25.07.2016, 21:04
Цитата Сообщение от notAll Посмотреть сообщение
Ну, тогда вот, я вынес реализацию в отдельный файл, но если перенести заголовок widget_impl.h в файл реализации, то возникают ошибки компиляции с фу-цией makeWidget.
разумеется.
шаблоны выносить нельзя.

сама попытка втиснуть impl в шаблон - ущербная
1
183 / 181 / 66
Регистрация: 15.02.2015
Сообщений: 515
25.07.2016, 23:04
При использовании pimpl в связке с unique_ptr можно обойтись дефолтными деструктором и перемещающими конструктором с оператором присваивания. Однако, так как в заголовочном файле о классе impl ничего не известно, то в файле реализации необходимо это указать явно:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* Widget.h */
class Widget
    class widget_impl;
    unique_ptr<widget_impl> pimpl;
public:
    ...
    Widget(Widget&&);
    Widget& operator=(Widget&&);
    ~Widget();
    ...
}
 
/* Widget.cpp */
class Widget::widget_impl
{
    //impl
}
...
Widget::Widget(Widget&&) = default;
Widget& Widget::operator=(Widget&&) = default;
Widget::~Widget() = default;
...
Копирующие конструктор и оператор присваивания дефолтные не прокатят, так как unique_ptr некопируемый, либо "удалить" эти методы, либо написать самому.
И ещё лучше вынести impl класс за пределы основного класса, использоваться на прямую он так и так не сможет, однако будет удобнее его разрабатывать.
1
495 / 209 / 70
Регистрация: 27.05.2016
Сообщений: 557
26.07.2016, 15:05  [ТС]
Еще остались у меня два маленьких вопроса:
1) Надо ли помечать ф-ции перемещения (17-18 строка) как noexcept или они по умолчанию будут такими?
2) Можно ли ка кто сделать чтобы не было конфликтов с двумя операторами присваивания(18, 33 строки)?
widget.h
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#ifndef WIDGET_H
#define WIDGET_H
 
#include <memory>
#include <utility>
#include <type_traits>
 
template <typename T, typename U>
class Widget
{
    class Widget_impl;
 
    std::unique_ptr<Widget_impl> impl = nullptr;
 
public:
    Widget() = default;
    Widget(Widget&&) noexcept = default;
    Widget& operator=(Widget&&) noexcept = default;
    ~Widget() = default;
 
    Widget(const T& t, const U& u) :
        impl(std::make_unique<Widget_impl>(t, u))
    {}
 
    Widget(T&& t, U&& u) :
        impl(std::make_unique<Widget_impl>(std::forward<T>(t), std::forward<U>(u)))
    {}
 
    Widget(const Widget& other) :
        impl(other.impl ? std::make_unique<Widget_impl>(*other.impl) : nullptr)
    {}
 
    Widget& operator=(Widget other)
    {
        swap(other);
        return *this;
    }
 
    void swap(Widget& other) noexcept
    {
        std::swap(impl, other.impl);
    }
};
 
#include "widget_impl.h"
 
template <typename T, typename U>
auto makeWidget(T&& t, U&& u)
{
    using TType = std::remove_reference_t<T>;
    using UType = std::remove_reference_t<U>;
    return Widget<TType, UType>(std::forward<T>(t), std::forward<U>(u));
}
 
#endif // WIDGET_H
widget_impl.h
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#ifndef WIDGET_IMPL_H
#define WIDGET_IMPL_H
 
template <typename T, typename U>
class Widget<T, U>::Widget_impl
{
    T t;
    U u;
public:
    Widget_impl(T&& t_, U&& u_)
        : t(std::forward<T>(t_)), u(std::forward<U>(u_))
    {}
 
    Widget_impl(const T& t_, const U& u_)
        : t(t_), u(u_)
    {}
};
 
#endif // WIDGET_IMPL_H
main
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include "widget.h"
 
struct Test {
    Test() = default;
    Test(Test const&)                { puts(__PRETTY_FUNCTION__); }
    Test(Test &&)                    { puts(__PRETTY_FUNCTION__); }
    Test & operator =(Test const&)   { puts(__PRETTY_FUNCTION__); return *this;}
    Test & operator =(Test &&)       { puts(__PRETTY_FUNCTION__); return *this;}
};
 
int main()
{
    Test test;
    auto wg = makeWidget(Test(), Test()); 
    auto wg2 = makeWidget(test, Test()); 
 
    wg = std::move(wg2); //ошибка: ambiguous overload for 'operator='
}
0
Эксперт С++
 Аватар для hoggy
8973 / 4319 / 960
Регистрация: 15.11.2014
Сообщений: 9,760
26.07.2016, 15:07
Цитата Сообщение от notAll Посмотреть сообщение
Widget& operator=(Widget other)
заменить на:

C++
1
const Widget& operator=(const Widget& other)
3
495 / 209 / 70
Регистрация: 27.05.2016
Сообщений: 557
16.08.2016, 14:20  [ТС]
Еще один маленький пример кода:
widget.h
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef WIDGET_H
#define WIDGET_H
 
#include <memory>
 
class Widget
{
    class WidgetImpl;
    std::unique_ptr<WidgetImpl> pimpl_;
 
public:
    Widget();
    ~Widget();
};
 
#endif // WIDGET_H
widget.cpp
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
#include "widget.h"
#include <string>
 
class Widget::WidgetImpl
{
public:
    std::string name;
};
 
Widget::Widget() : pimpl_(std::make_unique<WidgetImpl>())
{}
 
Widget::~Widget() {}
Вопрос: почему здесь обязательно надо определять деструктор (даже пустой) в файле реализации? В книге Саттера пишется такое:
заметим, что если мы используем член auto_ptr unique_ptr, то: а) мы
должны либо обеспечить определение widgetlmpl вместе с определением widget,
либо, если вы хотите скрыть widgetlmpl, вы должны написать свой собственный
деструктор widget, даже если это тривиальный деструктор;
Если вы используете деструктор, автоматически генерируемый компилятором, то этот
деструктор будет определен в каждой единице трансляции; следовательно, определение
Widgetlmpl должно быть видимо в каждом модуле трансляции.
У меня только один модуль трансляции - widget.cpp и WidgetImpl там определен. И почему это касаеться только деструктора, а как же генерируемые операции копирования-перемещения компилятором?
0
19500 / 10105 / 2461
Регистрация: 30.01.2014
Сообщений: 17,817
16.08.2016, 15:16
notAll, потому что delete для incomplete class type - это UB.
5.3.5/5:
if the object being deleted has incomplete class type at the point of deletion and the complete class has a non-trivial destructor or a deallocation function, the behavior is undefined.
1
495 / 209 / 70
Регистрация: 27.05.2016
Сообщений: 557
16.08.2016, 15:46  [ТС]
Одно не понятно - почему нельзя положиться на синтезируемый компилятором деструктор по умолчанию? Правильно я понимаю, синтезируемый деструктор будет размещен компиляторов в widget.h?
0
19500 / 10105 / 2461
Регистрация: 30.01.2014
Сообщений: 17,817
16.08.2016, 16:03
Цитата Сообщение от notAll Посмотреть сообщение
Одно не понятно - почему нельзя положиться на синтезируемый компилятором деструктор по умолчанию? Правильно я понимаю, синтезируемый деструктор будет размещен компиляторов в widget.h?
Потому что синтезируемый деструктор будет inline, при синтезировании ему будет доступна только та информация, которая указана в объявлении класса Widget. А в объявлении класса Widget класс WidgetImpl - неполный тип, значит будет сгенерирован деструктор, который вызывает деструктор std::unique_ptr<WidgetImpl>, который вызывает delete для неполного типа. Получаем UB. На практике, если это скомпилируется, то деструктор WidgetImpl просто не будет вызван.

Вот ты же сам писал цитировал перевод Саттера:
Цитата Сообщение от notAll Посмотреть сообщение
Если вы используете деструктор, автоматически генерируемый компилятором, то этот
деструктор будет определен в каждой единице трансляции; следовательно, определение
Widgetlmpl должно быть видимо в каждом модуле трансляции.
Как раз для предотвращения такиз ошибок существует идиома checked delete.

Добавлено через 1 минуту
Цитата Сообщение от notAll Посмотреть сообщение
а как же генерируемые операции копирования-перемещения компилятором?
А тут все еще проще: указатели на неполные типы можно спокойно копировать и присваивать.
1
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
16.08.2016, 16:03
Помогаю со студенческими работами здесь

Создать класс СПИСОК целых чисел. Разработать класс СТЕК
&quot;Создать класс СПИСОК целых чисел. Разработать класс СТЕК, который вмещает объект класса СПИСОК. Определить необходимые конструкторы,...

Разработать класс Tableware (посуда) и производный класс Dish (тарелка). Описать атрибуты
Разработать класс Tableware (посуда) и производный класс Dish (тарелка). Описать атрибуты.

Разработать класс Man (человек) и производный класс Student (студент). Описать атрибуты.
Разработать класс Man (человек) и производный класс Student (студент). Описать атрибуты.

Разработать класс для описанных ниже объектов. Включить в класс методы set (.), get (.), show (.)
Разработать класс для описанных ниже объектов. Включить в класс методы set (...), get (...), show (...) Patient: Фамилия, Имя, Отчество,...

В чем преимущество использования Pimpl?
Есть две реализации одной лабораторной. Первая с закрытым наследованием, вторая с использованием Pimpl. Делают одно и тоже. Объясните...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
16
Ответ Создать тему
Новые блоги и статьи
делаю науч статью по влиянию грибов на сукцессию
anaschu 13.03.2026
прикрепляю статью
SDL3 для Desktop (MinGW): Создаём пустое окно с нуля для 2D-графики на SDL3, Си и C++
8Observer8 10.03.2026
Содержание блога Финальные проекты на Си и на C++: hello-sdl3-c. zip hello-sdl3-cpp. zip Результат:
Установка CMake и MinGW 13.1 для сборки С и C++ приложений из консоли и из Qt Creator в EXE
8Observer8 10.03.2026
Содержание блога MinGW - это коллекция инструментов для сборки приложений в EXE. CMake - это система сборки приложений. Здесь описаны базовые шаги для старта программирования с помощью CMake и. . .
Как дизайн сайта влияет на конверсию: 7 решений, которые реально повышают заявки
Neotwalker 08.03.2026
Многие до сих пор воспринимают дизайн сайта как “красивую оболочку”. На практике всё иначе: дизайн напрямую влияет на то, оставит человек заявку или уйдёт через несколько секунд. Даже если у вас. . .
Модульная разработка через nuget packages
DevAlt 07.03.2026
Сложившийся в . Net-среде способ разработки чаще всего предполагает монорепозиторий в котором находятся все исходники. При создании нового решения, мы просто добавляем нужные проекты и имеем. . .
Модульный подход на примере F#
DevAlt 06.03.2026
В блоге дяди Боба наткнулся на такое определение: В этой книге («Подход, основанный на вариантах использования») Ивар утверждает, что архитектура программного обеспечения — это структуры,. . .
Управление камерой с помощью скрипта OrbitControls.js на Three.js: Вращение, зум и панорамирование
8Observer8 05.03.2026
Содержание блога Финальная демка в браузере работает на Desktop и мобильных браузерах. Итоговый код: orbit-controls-threejs-js. zip. Сканируйте QR-код на мобильном. Вращайте камеру одним пальцем,. . .
SDL3 для Web (WebAssembly): Синхронизация спрайтов SDL3 и тел Box2D
8Observer8 04.03.2026
Содержание блога Финальная демка в браузере. Итоговый код: finish-sync-physics-sprites-sdl3-c. zip На первой гифке отладочные линии отключены, а на второй включены:. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru