C++ Pimpl Idiom с использованием уже существующего класса

У нас есть кодовая база с большим количеством шаблонов, состоящая только из заголовков, к которой клиент хотел бы получить доступ. Например, допустим, он содержит класс Foo в заголовке foo.hpp:

#ifndef FOO_HEADER
#define FOO_HEADER

#include <iostream>

template <typename T>
struct Foo {

    Foo(){
       // Do a bunch of expensive initialization
    }

    void bar(T t){
        std::cout << t; 
    }

    // Members to initialize go here... 
};

#endif /* FOO_HEADER */

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

Одной из идей было бы использовать идиому PIMPL, чтобы обернуть этот основной код. В частности, мы могли бы создать класс FooWrapper с заголовком foo_wrapper.hpp:

#ifndef FOO_WRAPPER_HEADER
#define FOO_WRAPPER_HEADER

#include <memory>

struct FooWrapper {

    FooWrapper();
    ~FooWrapper();

    void bar(double t);

    private: 
        struct Impl;
        std::unique_ptr<Impl> impl;
};

#endif /* FOO_WRAPPER_HEADER */

и реализация foo_wrapper.cpp:

#include "foo.hpp"
#include "foo_wrapper.hpp"

struct FooWrapper::Impl {
    Foo<double> genie;
};

void FooWrapper::bar(double t){
    impl->genie.bar(t);
}

FooWrapper::FooWrapper() : impl(new Impl){
}

FooWrapper::~FooWrapper() = default;

Этот код работает так, как я ожидаю: https://wandbox.org/permlink/gso7mbe0UEOOPG7j

Однако есть одна мелочь, которая меня беспокоит. В частности, реализация требует того, что похоже на дополнительный уровень косвенности... Мы должны определить класс Impl для хранения члена класса Foo. Из-за этого все операции имеют эту косвенность формы impl->genie.bar(t);.

Было бы лучше, если бы мы могли каким-то образом сказать компилятору: «На самом деле Impl ЯВЛЯЕТСЯ классом Foo<double>», и в этом случае мы могли бы вместо этого сказать impl->bar(t);.

В частности, я думаю что-то вроде typedef или using, чтобы заставить это работать. Что-то типа

using FooWrapper::Impl = Foo<double>;

Но это не компилируется. Итак, к вопросам:

  1. Есть ли хороший способ избавиться от этой косвенности?
  2. Есть ли лучшая идиома, которую я должен использовать?

Я ориентируюсь на решение С++ 11, но С++ 14 тоже может работать. Важно помнить, что решение не может использовать заголовок foo.hpp в foo_wrapper.hpp. Каким-то образом мы должны скомпилировать этот код в библиотеку и распространять только скомпилированную библиотеку и заголовок foo_wrapper.

Вы беспокоитесь о стоимости выполнения косвенного обращения или о синтаксических «накладных расходах»?

Martin Ba 09.04.2019 17:02

Только синтаксические накладные расходы. Я не уверен, что будут какие-то значительные накладные расходы во время выполнения.

bremen_matt 09.04.2019 18:13
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
2
166
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Просто используйте Foo<double>:

// forward declaration so that you don't need to include "Foo.hpp"
template class Foo<double>;

struct FooWrapper {
    //...
    std::unique_ptr<Foo<double>> impl;
};

// explicit template instantiation so that Foo<double> exists without distributing "Foo.hpp"
template class Foo<double>;

void FooWrapper::bar(double t){
    impl->bar(t);
}

Для этого потребуется предоставить клиенту код foo.hpp. Мы не хотим этого делать, потому что это содержит проприетарный код.

bremen_matt 09.04.2019 15:27

Если я не ошибаюсь, ваш код foo_wrapper.hpp должен будет импортировать foo.hpp, чего я и пытаюсь избежать.

bremen_matt 09.04.2019 15:29

И наоборот, в том варианте, который я предлагаю, нам нужно только дать клиенту foo_wrapper.hpp и библиотеку, скомпилированную из foo_wrapper.cpp. Это в некоторой степени защищает проприетарный код (при условии, что они не будут декомпилировать код).

bremen_matt 09.04.2019 15:31
Ответ принят как подходящий

Вы можете просто объявить Foo в FooWrapper.h. Это позволит вам объявить для него std::unique_ptr:

#ifndef FOO_WRAPPER_HEADER
#define FOO_WRAPPER_HEADER

#include <memory>

// Forward declaration
template <typename T>
class Foo;

struct FooWrapper {
  FooWrapper();
  ~FooWrapper();

  void bar(double t);

 private:
  std::unique_ptr<Foo<double>> impl;
};

#endif /* FOO_WRAPPER_HEADER */

foo_wrapper.cc:

#include "foo_wrapper.h"
#include "foo.h"

void FooWrapper::bar(double t) {
  impl->bar(t);
}

FooWrapper::FooWrapper() : impl(std::make_unique<Foo<double>>()) {}

FooWrapper::~FooWrapper() = default;

Вы можете просто сделать std::unique_ptr<Foo<double>> impl; напрямую. Нет необходимости вводить псевдоним using и создавать экземпляр шаблона вручную. Тогда реализация ctor может просто сделать FooWrapper::FooWrapper() : impl(std::make_unique<Foo<double>>()) {}

Nikos C. 09.04.2019 17:31

@НикосС. упс, верно. В моем тестовом коде была "ошибка"

Mike van Dyke 09.04.2019 17:41

Другие вопросы по теме

Доступ к элементам в std::string, где позиция строки больше, чем ее размер
Как я могу выйти из потока, когда мой пользовательский интерфейс получает закрытый ввод в MFC
Использование конструктора по умолчанию в конструкторе не по умолчанию
Открытие нескольких потоков с объектом и возврат результата
Что не унаследовано от производного класса C++? По-видимому, оператор = и некоторые конструкторы фактически унаследованы
C++ - Проблема с сохранением значений - значения меняются после сохранения
Как записать в следующую строку файла .csv после того, как пользователь ввел значения без перезаписи предыдущих значений?
Делегирование конструкторов без инициализации
Есть ли способ поддерживать «константную ссылку» в качестве параметра сигнатуры функции в моем универсальном дизайне функционального маршрутизатора?
Всегда ли эквивалентны std::error_code и std::error_condition, созданные из одного и того же значения и одной и той же категории?