c++ 从抽象类派生的类中获取从抽象类派生的类的可选值

50few1ms  于 11个月前  发布在  其他
关注(0)|答案(2)|浏览(81)

我有一些类似于下面的代码,其中一组具有类似共享行为的类(Tool1,Tool2),都继承自一个抽象类(ITool)。所有这些类都拥有自己的相应类(Tool1Attachment,Tool2Attachment),这些类也继承自一个抽象类(IT附件)。

class ITool {
 private:
  virtual ? DoGetAttachment() = 0;
  // Other shared behaviours...

 public:
  ? GetAttachment() {return DoGetAttachment();}
  // Other shared behaviours...
};

class Tool1: public ITool {
  std::optional<Tool1Attachment> opt_attachment;

  ? DoGetAttachment() override;
  
 public:
  [...]
};

class Tool2: public ITool {
  std::optional<Tool2Attachment> opt_attachment;
  
  ? DoGetAttachment() override;
  
 public:
  [...]
};

// --------------------------------

class IAttachment {
  [...]
};

class Tool1Attachment : public IAttachment {
  [...]
};

class Tool2Attachment : public IAttachment {
  [...]
};

字符串
工具类有一个可选的上下文是有意义的,在任何给定的时间它可能有也可能没有一个实际的示例。
如果我有一个IAttachment,并希望获得IAttachment,就会出现问题。
我最初使用的是指针,即IAttachment* GetAttachment();,它工作得很好。然而,它似乎失去了optional提供的一些更明确的缺失值检查,使得将来更容易遇到nullptr问题。
我还尝试了不同的结构(std::reference_wrapper)与可选,但不断遇到无效的协变错误。
有没有一种方法可以允许返回一个可选的(或类似的)结构?返回类型应该是什么样的?或者带nullptr检查的指针最适合这里?

? GetAttachment() {return DoGetAttachment();}

2eafrhcq

2eafrhcq1#

指针是显而易见的解决方案(如果可选项为空,则使用空指针)。如果使用继承,则需要指针(或引用)无论如何,为了使多态性工作,C++支持协变返回类型,所以Owner1可以返回一个Owned1*。检查返回的指针是否为空是标准的做法。另一种方法是返回一个optional<IOwned*>(因为协方差在这里不起作用),但我看不出这会给你带来什么,除了额外的复杂性。

fxnxkyjh

fxnxkyjh2#

答案实际上归结为你对DoGetAttachment函数的期望。它应该把成员的“可选性”* 转发给调用者,并负责检查值是否存在?或者如果存储的std::optional有值,DoGetAttachment函数应该返回一个引用,如果没有值,则抛出一个异常?
假设你想把可选性“转发”给函数的调用者,你基本上有两个选择。

选项1:使用(原始)指针

使用指针,就像你已经根据你的问题所做的那样。这是最简单的解决方案和标准做法。

选项二:std::optionalstd::reference_wrapper一起使用

std::optional不是多态的,所以你不能将std::optional<Derived>转换为std::optional<Base>。你可以做的是返回一个std::optional<Base*>std::optional<std::reference_wrapper<Base>>。恕我直言,我更喜欢std::reference_wrapper,因为否则你会在两个级别上拥有“optionalness”属性:一次用于std::optional,一次用于存储指针。所以你需要检查两次是否存在。
选项2给你的代码带来了一些额外的复杂性,这取决于你是否值得这样做。
使用std::optional的理由是,

  • 返回值的“可选性”是通过语义来传达的。返回的指针不会自动传达这一信息;
  • 返回一个std::optional允许你使用std::optional的一元运算符,比如value_orand_thentransform(其中一些是C++23特性);
  • 与拥有(原始)指针相反,std::optional s不执行任何动态内存分配 (如果你需要的话)

下面是一个实现,其中DoGetAttachment返回std::optional<std::reference_wrapper<IToolAttachment>>

#include <optional>
#include <iostream>

struct IAttachment {
    virtual ~IAttachment(){}
    virtual std::string foo() const = 0;
};

struct Tool1Attachment : IAttachment 
{ 
    std::string foo() const override { return "Tool1Attachment"; }; 
};
struct Tool2Attachment : IAttachment
{ 
    std::string foo() const override { return "Tool2Attachment"; }; 
};

struct ITool {
  virtual ~ITool(){}

  using IAttachmentCRef = std::reference_wrapper<IAttachment const>;
  virtual std::optional<IAttachmentCRef> DoGetAttachment() const = 0;

};

template <typename T>
struct Tool: public ITool {
  std::optional<T> opt_attachment;

  std::optional<IAttachmentCRef> DoGetAttachment() const override
  {
    return opt_attachment.transform([](auto& v){ return std::ref(v); });
  }
  
};

using Tool1 = Tool<Tool1Attachment>;
using Tool2 = Tool<Tool2Attachment>;

int main()
{
    auto print = [](ITool const& tool) {
        return tool.DoGetAttachment()
            .transform([](auto v){ return v.get().foo(); })
            .value_or("No value.");
    };

    Tool1 tool1;

    Tool2 tool2;
    tool2.opt_attachment = Tool2Attachment();
    
    std::cout << print(tool1) << "\n";
    std::cout << print(tool2) << "\n";
}

字符串
输出量:

No value.
Tool2Attachment


Live Code Demo

相关问题