У нас есть кодовая база с большим количеством шаблонов, состоящая только из заголовков, к которой клиент хотел бы получить доступ. Например, допустим, он содержит класс 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>;
Но это не компилируется. Итак, к вопросам:
Я ориентируюсь на решение С++ 11, но С++ 14 тоже может работать. Важно помнить, что решение не может использовать заголовок foo.hpp
в foo_wrapper.hpp
. Каким-то образом мы должны скомпилировать этот код в библиотеку и распространять только скомпилированную библиотеку и заголовок foo_wrapper
.
Только синтаксические накладные расходы. Я не уверен, что будут какие-то значительные накладные расходы во время выполнения.
Просто используйте 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
. Мы не хотим этого делать, потому что это содержит проприетарный код.
Если я не ошибаюсь, ваш код foo_wrapper.hpp
должен будет импортировать foo.hpp
, чего я и пытаюсь избежать.
И наоборот, в том варианте, который я предлагаю, нам нужно только дать клиенту foo_wrapper.hpp
и библиотеку, скомпилированную из foo_wrapper.cpp
. Это в некоторой степени защищает проприетарный код (при условии, что они не будут декомпилировать код).
Вы можете просто объявить 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>>()) {}
@НикосС. упс, верно. В моем тестовом коде была "ошибка"
Вы беспокоитесь о стоимости выполнения косвенного обращения или о синтаксических «накладных расходах»?