实现模板专用函数

Implementing template specialized functions

我正在尝试允许 class 实现一个接口,该接口被模板化为允许或不允许内部更新。如果实现在 class 声明中,代码看起来像这样并且可以工作。如果它被移出(如此处所示),我会在 Visual Studio 2019 收到编译器错误,我不明白为什么。感谢所有帮助!

以下代码将无法编译。如果将 class 实现和 headers 修改为仅使用我注释掉的代码,它会按预期编译和工作。

#include <atomic>
#include <cstdint>
#include <memory>
#include <iostream>
#include <string>


enum class EntryType { Read, ReadUpdate };

template <EntryType Type>
class IXQ
{
public:
    virtual float GetValue() = 0;
};

class X : public IXQ<EntryType::Read>, public IXQ<EntryType::ReadUpdate>
{
public:
    float IXQ<EntryType::Read>::GetValue();
    /*
    float IXQ<EntryType::Read>::GetValue()
    {
        return _x;
    }
    */
    float IXQ<EntryType::ReadUpdate>::GetValue();
    /*
    float IXQ<EntryType::ReadUpdate>::GetValue() {
        _counter++;
        return _x;
    }
    */
    float _x = 10.0F;
    std::atomic<std::int32_t> _counter = 0;
};

float X::IXQ<EntryType::Read>::GetValue()
{
    return _x;
}

float X::IXQ<EntryType::ReadUpdate>::GetValue()
{
    _counter++;
    return _x;
};


int main(int argc, char* argv[])
{
    {
        auto k = std::make_unique<X>();

        auto ptrQ = static_cast<IXQ<EntryType::Read>*>(k.get());

        std::cout << std::to_string(ptrQ->GetValue()) << std::endl;

        std::cout << k->_counter.load() << std::endl;
    }

    {
        auto k = std::make_unique<X>();

        auto ptrQ = static_cast<IXQ<EntryType::ReadUpdate>*>(k.get());

        std::cout << std::to_string(ptrQ->GetValue()) << std::endl;

        std::cout << k->_counter.load() << std::endl;
    }


    return 0;
}

我认为这可以通过添加类的中间层来解决,假设X_ReadX_ReadUpdate可以继承X

      IXQ
     /   \
X_Read   X_ReadUpdate
     \   /
       X

诀窍是让那些中间 类 通过调用两个虚拟纯方法实现 GetValue,假设分别是 GetValueReadGetValueReadUpdate。这样,X仍然继承了IXQ<EntryType::Read>IXQ<EntryType::ReadUpdate>,但是实现了GetValueReadGetValueReadUpdate,这两个方法接口清晰明了。

[Demo]

template <EntryType Type>
class IXQ
{
public:
    virtual float GetValue() = 0;
};

class X_Read : public IXQ<EntryType::Read>
{
public:
    virtual float GetValue() override { return GetValueRead(); };
    virtual float GetValueRead() = 0;
};

class X_ReadUpdate : public IXQ<EntryType::ReadUpdate>
{
public:
    virtual float GetValue() override { return GetValueReadUpdate(); };
    virtual float GetValueReadUpdate() = 0;
};

class X : public X_Read, public X_ReadUpdate
{
public:
    virtual float GetValueRead() override { return _x; }
    virtual float GetValueReadUpdate() { _counter++; return _x; };
    float _x{10.0f};
    std::atomic<std::int32_t> _counter{};
};

// Outputs:
//   10.000000
//   0
//   10.000000
//   1

1。 Microsoft 特定语法

您用来覆盖 GetValue() 的语法是 不是 标准 c++。

class X : public IXQ<EntryType::Read> {
public:
    float IXQ<EntryType::Read>::GetValue() { /* ... */ }
}

这是 Microsoft 特定的 C++ 扩展,称为 Explicit Overrides (C++) and is intended to be used with __interfaces(也是 Microsoft 特定的 c++ 扩展)。

__interfaces 不正式支持 c++ 模板,因为它们主要用于 COM 接口,例如:

[ object, uuid("00000000-0000-0000-0000-000000000001"), library_block ]
__interface ISomething {
   // properties
   [ id(0) ] int iInt;
   [ id(5) ] BSTR bStr;

   // functions
   void DoSomething();
};

令人惊讶的是 float IXQ<EntryType::Read>::GetValue() { /* ... */ } 完全有效。


2。符合标准 C++ 的解决方案

在标准 C++ 中,被覆盖的函数仅由名称和参数决定,因此您唯一的选择是同时覆盖它们。

例如:

class X : public IXQ<EntryType::Read>, public IXQ<EntryType::ReadUpdate>
{
public:
    // will be the implementation for both IXQ<EntryType::Read> & IXQ<EntryType::ReadUpdate>
    float GetValue() override;
};

float X::GetValue() {
    /* ... */
}

另一种符合标准的方法是创建 2 个 class 继承给定接口的类,然后让 X 从它们两个继承:

class XRead : public IXQ<EntryType::Read> {
public:
    float GetValue() override { /* ... */ }
};

class XReadUpdate : public IXQ<EntryType::ReadUpdate> {
public:
    float GetValue() override { /* ... */ }
};

class X : public XRead, public XReadUpdate {
    /* ... */
};

如果你想在 XReadXReadUpdate 之间共享状态,你必须引入另一个级别,例如:

class XBase {
public:
    virtual ~XBase() = default;
protected:
    float value;
};

class XRead : public virtual XBase, public IXQ<EntryType::Read> {
    float GetValue() override { return value; }
};

class XReadUpdate : public virtual XBase, public IXQ<EntryType::ReadUpdate> {
    float GetValue() override { return value; }
};

class X : public XRead, public XReadUpdate {
    /* ... */
};

3。 API 设计

的可能建议

请记住,这种 API 设计使用起来会相当复杂,因为您总是需要先转换到特定接口,然后才能调用该函数,因为 X{}.GetValue() 会模棱两可。

例如:

X x;

IXQ<EntryType::Read>& read = x;
std::cout << read.GetValue() << std::endl;

IXQ<EntryType::ReadUpdate>& update = x;
std::cout << update.GetValue() << std::endl;

// this is *not* possible, GetValue() is ambigous
// std::cout << x.GetValue() << std::endl;

我建议将两个接口分开并使用不同的方法名称,例如:godbolt example

struct IXQReadonly {
    virtual ~IXQReadonly() = default;
    virtual float GetValue() = 0;
};

struct IXQ : IXQReadonly {
    virtual float GetValueUpdate() = 0;
};

class X : public IXQ {
public:
    X() : value(0.0f), counter(0) { }

    float GetValue() override { return value; }
    float GetValueUpdate() override {
        ++counter;
        return value;
    }

private:
    float value;
    std::atomic<int> counter;
};

这带来了一整套好处:

  • 可以直接在X上调用GetValue()/GetValueUpdate(),不需要先转成接口
    X x;
    x.GetValue();
    x.GetValueUpdate();
    
  • 如果它会更新计数器,从方法名称中可以立即清楚
  • 给定 IXQ 的函数可以调用期望 IXQReadonly 的函数,但反过来不行:(因此您可以将读写接口“降级”为只读,但不是将只读接口“升级”为读写)
    void bar(IXQReadonly& r) { r.GetValue(); /* we can't do GetValueUpdate() here */ }
    void foo(IXQ& x) { bar(x); x.GetValueUpdate(); }
    

另外记得将析构函数声明为 virtual(至少在最顶层的 class / 接口中),否则如果您尝试删除 [= 的实例,往往会发生有趣的事情28=] 通过指向其基/接口之一的指针。