C ++ Pimpl Idiom с использованием уже существующего класса - PullRequest
3 голосов
/ 09 апреля 2019

У нас есть сильно кодированная база кодов только для заголовков, к которой клиент хотел бы получить доступ. Например, предположим, что он содержит класс 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. Есть ли лучшая идиома, которую я должен использовать?

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

Ответы [ 2 ]

3 голосов
/ 09 апреля 2019

Вы можете просто объявить форвард 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;
1 голос
/ 09 апреля 2019

Просто используйте 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);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...