【问题标题】:extensible library interface without reinterpret_cast无需 reinterpret_cast 的可扩展库接口
【发布时间】:2015-05-16 18:23:04
【问题描述】:

我正在构建一个静态库,它将用于许多未来的项目。我不想在这个静态库中限制特定函数的接口,以便应用程序代码在数据类型上具有灵活性。这个库将保存指向用户需要的对象的纯虚拟基类指针。

起初,我尝试模板化这个函数。但是,我还必须对纯虚拟基函数进行模板化(在显示的代码中,BBInterface::Go)——显然这是不可能的。

访问者模式听起来可能适用,但恐怕我就是不明白。此外,我不明白我是否可以将静态库保持为黑盒状态,或者是否必须重新编译静态库并在任何时候有人添加可能的数据类型时与一组新的“访问者”链接。

我现在尝试在函数内部创建一个模板化结构,然后将其重新解释为(希望?)等效结构。见下文

这似乎适用于两个输入(A 和 B)。这没关系,但理想情况下,我希望使用可变参数模板来获得许多输入。在这一点上,这超出了我的想象。如果有人能提供帮助,那就太好了。

那么,有没有更优雅的方式来保持可扩展的函数接口(BBContainer::Do 在下面的代码中)?有没有办法避免 reinterpret_cast?我可以将其扩展到两个以上的模板化参数吗?有没有办法像 dynamic_cast 一样检查 reinterpret_cast 是否成功?

#include <iostream>
#include <vector>
#include <map>
#include <memory>
using namespace std;

static const double values[] = {0., 1., 2., 3., 4., 5., 6. };

// ------ ASSUME THIS BLACK BOX AREA IS IN A STATIC LIBRARY THE USER CAN NOT MODIFY -------//
struct BBPacket {};

class BBInterface
{
public:
    virtual void Go(BBPacket&) = 0;
};

class BBContainer
{
public:
    void Add(const string aName, std::unique_ptr<BBInterface>&& aThing)
    {
        BBMap[aName] = std::move(aThing);
    }

    template <typename A, typename B>
    void Do(const std::string& aName, A& aVal, const B& aIndex)
    {
        struct NewPacket : public BBPacket
        {
            NewPacket(A& aVal, const B& aIndex) : mVal(aVal), mIndex(aIndex) {}
            A& mVal;
            const B& mIndex;
        };
        NewPacket temp(aVal, aIndex);
        this->Do(aName, temp);

    }

    void Do(const string& aName, BBPacket& aPacket)
    {
        BBMap[aName]->Go(aPacket);
    }

private:
    map<std::string, unique_ptr<BBInterface>> BBMap;
};


// ----- The user area is written by the user, and should not be included in the blackbox project! ---------
struct USingleValuePacket
{
    double& mVal;
    const int& mIndex;
};

struct UVectorValuePacket
{
    vector<double>& mVals;
    const vector<int>& mIndices;
};


class USingleExtractor : public BBInterface
{
    virtual void Go(BBPacket& aPacket)
    {
        USingleValuePacket& danger = reinterpret_cast<USingleValuePacket&>(aPacket);
        fprintf(stdout, "The current single value is %1.1f\n", danger.mVal);
        danger.mVal = values[danger.mIndex];
    }
};

class UVectorExtractor : public BBInterface
{
    virtual void Go(BBPacket& aPacket)
    {
        UVectorValuePacket& danger = reinterpret_cast<UVectorValuePacket&>(aPacket);
        for (int i = 0; i < danger.mVals.size(); ++i)
        {
            fprintf(stdout, "The current vector value %i is %1.1f\n",i, danger.mVals[i]);
            danger.mVals[i] = values[danger.mIndices[i]];
        }
    }
};


int main()
{
    BBContainer a;
    a.Add("f", std::unique_ptr<USingleExtractor>(new USingleExtractor));
    a.Add("g", std::unique_ptr<UVectorExtractor>(new UVectorExtractor));
    double val = 0.;
    int index = 4;
    a.Do("f", val, index);
    fprintf(stdout, "Expected value is 4.0 and I get %1.1f\n", val);

    std::vector<double> valVec(3);
    std::vector<int> indexVec; indexVec.push_back(0);  indexVec.push_back(2); indexVec.push_back(5);
    a.Do("g", valVec, indexVec);
    fprintf(stdout, "Expected value for index 0 is 0.0 and I get %1.1f\n", valVec[0]);
    fprintf(stdout, "Expected value for index 1 is 2.0 and I get %1.1f\n", valVec[1]);
    fprintf(stdout, "Expected value for index 2 is 5.0 and I get %1.1f\n", valVec[2]);

//  a.Do("g", val, index); // This will go into UVectorExtractor with USingleValuePacket data - Not compatible!

    return 0;
}

编辑:

我希望 BBContainer::Do 有一个灵活的签名(在这个例子中我使用 (string, double&, const int&) 和 (string, vector&, const vector&),但我可能还有更多)。同时我不想修改 BBInterface(例如用 Go(double&, const int), Go(vector&, const vector&) 等等)。我可以假设 BBInterface 的派生类知道其特定的 Go 实现需要哪些数据。 那么,当 BBContainer::Do 只能访问 BBInterface 基类(不允许专门化)时,如何将通用数据从 BBContainer::Do 转发到 BBInterface 的派生类?而且,有没有比在 BBInterface 基类中生成模板结构并在其派生类中使用 reinterpret_cast 更类型安全的方法?

【问题讨论】:

  • 您的实际要求是什么?我很难从您的特定实施尝试中准确推断出您希望提供什么功能。我认为您的问题需要在更高的设计水平上得到回答——例如确定什么需要是运行时多态性,什么需要是编译时多态性,以及提供什么语法糖——而不是“用其他东西替换这部分实现”的形式。
  • @Hurkyl 我会尝试在编辑中更好地解释它。
  • reinterpret_cast 在几乎所有人们无论如何都想使用它的情况下都是错误的。
  • 您似乎希望BBContainer::Do 具有灵活签名的主要原因是它可以构造一个数据包——为什么不直接接受一个数据包作为输入呢?如果类型正确,您可以制作数据包如何将自己发送到BBInterface&amp;。 (如果你真的想的话,你可以元编程一些语法糖)
  • 我应该指出,您的编辑并没有讨论需求:您似乎仍然在考虑如何以特定方式实现某些东西的问题,而不是您希望实现的功能提供。

标签: c++ static-libraries reinterpret-cast


【解决方案1】:

正如赫尔基尔指出的,我应该做一个小包。这似乎已经足够好,还有一个辅助函数可以保持Do 的界面干净和dynamic_cast 而不是reinterpret_cast。我仍在研究可变长度数据包的可变参数模板。

新数据包:

template<typename A, typename B>
struct UPacket : public BBPacket
{
    UPacket(A& aVal, const B& aIndex) : mVal(aVal), mIndex(aIndex) {}
    A& mVal;
    const B& mIndex;
};

辅助函数:

template <typename A, typename B>
void Do(BBContainer& a, const string& aName, A& aVal, const B& aIndex)
{
    a.Do(aName, UPacket<A, B>(aVal, aIndex));
}

用法:

...
double val = 0.;
int index = 4;
Do(a,"f", val, index);
...
std::vector<double> valVec(3);
std::vector<int> indexVec; indexVec.push_back(0);  indexVec.push_back(2); indexVec.push_back(5);
Do(a, "g", valVec, indexVec); 

【讨论】:

    猜你喜欢
    • 2019-11-17
    • 1970-01-01
    • 1970-01-01
    • 2020-11-15
    • 2017-10-21
    • 1970-01-01
    • 2015-06-21
    • 2021-02-06
    • 2015-08-09
    相关资源
    最近更新 更多