【问题标题】:Determine Class Implementation Dynamically via Constructor通过构造函数动态确定类实现
【发布时间】:2019-03-31 03:23:06
【问题描述】:

我想创建一个行为特定的类 - 例如从函数double getValue(const int& x) const 中吐出某些值 - 基于传递给其构造函数的“类型”。现在我有两种方法:

  1. 存储传入的“类型”,然后每次调用getValue 中的switch 语句,以确定使用哪个实现。
  2. 对传入的“类型”(在构造函数中)使用switch 语句来创建表示所需实现的内部对象。所以getValue 本身不再需要switch

每次调用getValue 时都会调用switch,因此方法1“似乎”效率低下。方法 2 似乎有些笨拙,因为我需要使用 <memory>,而且它还使得复制/分配我的课程变得不简单。

还有其他更简洁的方法可以解决此类问题吗?

代码示例:

#include <memory>

enum class ImplType { Simple1, Simple2 /* more cases */ };

class MyClass1
{
private:
    const ImplType implType;

public:
    MyClass1(const ImplType& implType) : implType(implType) { }

    double getValue(const int& x) const
    {
        switch (implType)
        {
        case ImplType::Simple1: return 1; /* some implemention */
        case ImplType::Simple2: return 2; /* some implemention */
        }
    }
};

class MyClass2
{
private:
    struct Impl { virtual double getValue(const int& x) const = 0; };
    struct ImplSimple1 : Impl { double getValue(const int& x) const override { return 1; /* some implemention */ } };
    struct ImplSimple2 : Impl { double getValue(const int& x) const override { return 2; /* some implemention */ } };

    const std::unique_ptr<Impl> impl;

public:
    MyClass2(const ImplType& implType) : impl(std::move(createImplPtr(implType))) { }

    static std::unique_ptr<Impl> createImplPtr(const ImplType& implType)
    { 
        switch (implType)
        {
        case ImplType::Simple1: return std::make_unique<ImplSimple1>();
        case ImplType::Simple2: return std::make_unique<ImplSimple2>();
        }
    }

    double getValue(const int& x) const { return impl->getValue(x); }
};


int main()
{
    MyClass1 my1(ImplType::Simple1);
    MyClass2 my2(ImplType::Simple1);

    return 0;
}

【问题讨论】:

  • 听起来你想要一个虚拟方法(或抽象),然后在子类中以不同的方式实现它
  • MyClass2 是否符合您的意思?基本上,我想避免每个实现都有多个MyClasses,并且只有一个入口点,即一个构造函数。
  • 为什么要避免不同的类?
  • 假设我有 100 种不同的实现,那么我希望用户通过 enum 的构造函数指定所需的实现(简单直接),而不是让用户记住 100 种不同的实现类名。此外,MyImpl1 impl = MyImpl2();MyImpl10 impl = MyImpl5(); 的行为不如 MyImpl impl = MyImpl(Type2);MyImpl impl = MyImpl(Type5); 清晰。
  • 没有必要记住 100 个不同的类名,知道枚举就足够了。看我的回答

标签: c++ class inheritance dynamic c++17


【解决方案1】:

您的代码基本上是在模仿虚拟方法(草率地说:相同的接口,但在运行时选择实现),因此如果您确实使用虚拟方法,您的代码会更简洁:

 #include <memory>

 struct base {
     virtual double getValue(const int& x) const = 0;
 };

 struct impl1 : base {
     double getValue(const int& x) { return 1.0; }
 };

 struct impl2 : base {
     double getValue(const int& x) { return 2.0; }
 };
 // ... maybe more...

 enum select { impl1s, impl2s };
 base* make_impl( select s) {
     if (s == impl1s) return new impl1();
     if (s == impl2s) return new impl2();
 }

 int main() {
     std::shared_ptr<base> x{ make_impl(impl1) };
 }

不确定这是否是您要查找的内容。顺便说一句,使用&lt;memory&gt; 不应该让您觉得“笨拙”,而是应该为我们在 c++ 中拥有如此出色的工具而感到自豪;)。

编辑:如果您不希望用户使用(智能)指针,则将上述内容包装在另一个类中:

struct foo {
    shared_ptr<base> impl;
    foo( select s) : impl( make_impl(s) ) {}
    double getValue(const int& x) { return impl.getValue(x); }
};

现在用户可以做

int main() {
    auto f1 { impl1s };
    auto f2 { impl2s };
    f1.getValue(1);
    f2.getValue(2);
}        

【讨论】:

  • 如果我不希望 x 成为指针 std::shared_ptr 而是实际类型怎么办?
  • 什么意思? shared_ptr&lt;base&gt; “实际类型”。我觉得您对智能指针有一些错误的偏见。使用它们没有什么不好
  • 我很欣赏你的想法,但我基本上想对用户隐藏继承和抽象细节。因此,我想要一个在运行时接受用户输入(无论是字符串还是枚举)的类,然后根据用户输入类型所暗示的内容进行行为。
  • @Phil-ZXX 对不起,我不明白你想说什么。继承和实现对用户是隐藏的。他们只需要知道枚举并调用make_impl,他们永远不需要看到impl 类型
【解决方案2】:

如果你有一组封闭的类型可供选择,你想要std::variant

using MyClass = std::variant<MyClass1, MyClass2, MyClass3, /* ... */>;

它不使用动态分配 - 它基本上是union 的类型安全现代替代方案。

【讨论】:

    【解决方案3】:

    更多面向对象的方法:

    class Interface
    {
        public:
            virtual int getValue() = 0;
    };
    
    class GetValueImplementation1 : public Interface
    {
        public:
            int getValue() {return 1;}
    };
    
    class GetValueImplementation2 : public Interface
    {
        public:
            int getValue() {return 2;}
    };
    
    class GeneralClass
    {
        public:
            GeneralClass(Interface *interface) : interface(interface) {}
            ~GeneralClass() 
            {
                if (interface)
                    delete interface;
            }
    
            int getValue() { return interface->getValue(); }
    
        private:
            Interface *interface;
    };
    

    因此,在这种情况下,您可以在没有任何指针的情况下使用它:

    int main()
    {
        GeneralClass obj1(new GetValueImplementation1());
        GeneralClass obj2(new GetValueImplementation2());
    
        cout << obj1.getValue() << " " << obj2.getValue();
        return 0;
    }
    

    输出将是:

    1 2
    

    但在这种情况下,您应该小心使用空指针或在GeneralClass 中使用智能指针。

    【讨论】:

    • 这看起来有点像我的方法2?
    • @Phil-ZXX,看起来像。但是面向对象的方法更灵活。如果您使用MyClass2,则必须更改大量代码才能添加实现。但是使用上述方法,您只需要创建您想要实现 Interface 的内容,而无需对 GeneralClass 或其他实施进行任何更改。
    猜你喜欢
    • 2021-05-24
    • 2015-03-15
    • 2013-09-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-01
    • 2018-10-17
    相关资源
    最近更新 更多