Использование языка PIMPL с шаблонами функций-членов (без предварительного знания всех возможных типов данных) - PullRequest
1 голос
/ 07 июня 2019

Я пытаюсь реализовать универсальный интерфейс с использованием языка PIMPL, для которого требуются некоторые функции-члены шаблона, чтобы обеспечить функциональность для типа ANY . Проблема в том, что я не могу найти какой-либо способ поддержки использования шаблонных функций с идиомой PIMPL без предварительного объявления всех возможных типов, которые может использовать функция.

См. Связанный вопрос: Идиома Pimpl с функцией-членом шаблона

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

example.h

#ifndef EXAMPLE_H
#define EXAMPLE_H

#include <memory>

class Example
{
public:
    Example();
    ~Example();

    // Generic template function in the interface
    template <typename T>
    void PrintData(const T& data) const;

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

#endif

example.cpp

#include "example.h"
#include <iostream>

// Private implementation
struct Example::Impl
{
    template <typename T>
    void PrintData(const T& data) const
    {
        std::cout << data << std::endl;
    }
};

// Note: using C++11 so "make_unique" is not available
Example::Example() : m_impl(new Impl()) {}
Example::~Example() = default;

// Forward the method call to the implementation
template <typename T>
void Example::PrintData(const T& data) const
{
    m_impl->PrintData(data);
}

// Don't want to have to explicitly state all types...
// this is not practical because "PrintData" should work for ANY printable type.

// Uncommenting the line below will allow this example to compile,
// but it will only compile when used with "int" data.
//template void Example::PrintData(const int& data) const;

main.cpp

#include "example.h"

int main()
{
    Example ex;
    ex.PrintData(42);
}

Попытка скомпилировать это не удастся с неопределенной ошибкой ссылки:

g++ -std=c++11 -o main main.cpp example.cpp
/tmp/cc6IhZsx.o: In function `main':
main.cpp:(.text+0x2b): undefined reference to `void Example::PrintData<int>(int const&) const'
collect2: error: ld returned 1 exit status

Этот пример предоставляет очень общую функцию в интерфейсе, которая должна работать для ЛЮБОГО печатного типа. Это создает проблему, поскольку мы не можем реально предсказать все типы, на которых эта функция будет использоваться до момента компиляции.

Как заставить такую ​​функцию работать с идиомой PIMPL (или каким-либо другим способом создания " брандмауэра компилятора ")

Обратите внимание, что этот пример намеренно тривиален для демонстрации проблемы. В реальном мире у меня есть классы, которые содержат НАМНОГО более сложные шаблонные функции в интерфейсе, которые должны работать на многих типах. Из-за этого мне не удалось получить эти классы за брандмауэром компилятора, как обеспечивает идиома PIMPL.

Я открыт для рассмотрения альтернатив идиоме PIMPL, которые могут достичь желаемого эффекта, если такая вещь существует.

1 Ответ

0 голосов
/ 07 июня 2019

Если вы должны использовать идиому PImpl и поддерживать любой тип, лучшее, что вы можете сделать, - это стереть поведение.Это позволяет скрыть часть реализации, но не все.

В простых случаях std::function или предлагаемый std::function_ref могут работать:

#include <memory>
#include <ostream> // We must define some of the behavior in the header file.
#include <functional> // for std::function

class Example
{
public:
    Example();
    ~Example();

    // Generic template function in the interface
    template <typename T>
    void PrintData(const T& data) const;

private:
    struct Impl;
    std::unique_ptr<Impl> m_impl;

    // Some alternatives:
    // Some cost:
    void PrintDataImpl(const void* obj,
         const std::function<void(std::ostream&, const void*)>&) const;
    // Not yet standardized:
    void PrintDataImpl(const void* obj,
         std::function_ref<void(sstd::ostream&, const void*)>) const;
    // C-style:
    void PrintDataImpl(const void* obj,
         void(*fn)(std::ostream&, const void*)) const;
};

template <typename T>
void Example::PrintData(const T& data) const
{
    // std::function is expensive, but in this case, we fit in the small buffer
    // optimization as a lambda with no captures.
    // If we used `std::function_ref`, this would be cheaper.
    // If we used the C-style, the lambda cannot capture anything.
    PrintDataImpl(&data, [](std::ostream& out, const void* obj) {
        out << *static_cast<const T*>(obj);
    });
}


// In Example.cpp:
void Example::PrintDataImpl(const void* obj,
     const std::function<void(std::ostream&, const void*)>& fn) const
{
    // Call the function to apply the one piece of type-erased behavior.
    fn(std::cout, obj);
    std::cout << std::endl;
}
...