执行特定功能取决于类型

时间:2017-08-04 14:37:00

标签: c++

我想创建一个可以处理不同类型的东西的函数,它取决于所有类型。

我知道重载是一个很好的解决方案。就像,

class C1 {...};
class C2 {...};    
void handle(C1& c1){...}
void handle(C2& c2){...}

但是在重载方式中有很多重复的代码,因为这两个初始化是相同的。这就是我想把它们包装在一起的原因。

我有一些想法可以达到我的目的。 例如,

class C1 {...};
class C2 {...};

void handle_C1(C1 &c1);
void handle_C2(C2 &c2);

template<typename T>
void handle(T &content){
// Initialization was done here
if (std::is_same(C1, T))
   handle_C1(content);
if (std::is_same(C2, T))
   handle_C2(content);
}

发现编译错误错误 handle_C2不匹配参数因为当我调用时handle_C2参数类型为C2

C1 c1;
handle_C1<C1>(c1);

根据C ++中的SFINAE,我希望编译会忽略这种替换失败,但事实并非如此。

有人可以给我一个建议吗?

最好的解决方案是重载吗?如果是的话,我怎样才能减少重复的代码。

6 个答案:

答案 0 :(得分:28)

在我看来,你正在过度思考这个问题。只需在没有初始化代码的情况下定义重载,并定义一个利用重载决策的handle函数。

class C1 {...};
class C2 {...};    
void handle_impl(C1& c1){...} // Remove initialization from your overloads
void handle_impl(C2& c2){...}

template<typename T>
void handle(T &content)
{
    // Initialization is done here

    // Let the compiler resolve overloads for you
    handle_impl(content);
}

答案 1 :(得分:3)

当使用T实例化handle()时,将实例化该函数的完整主体。编译了“if”和它们的正文。虽然你和我知道is_same()是一个编译时常量,你可能希望编译器忽略不可能的情况,它的使用方式是作为运行时值,编译器必须处理ifs和在语义上检查“好像”它们都可能被调用(即使优化器可以消除一个,这是在编译器进行有效性测试之后。)因此,你最终得到了调用handle_C1和handle_C2的代码,两者都传递了相同的类型,一个是肯定是无效的,无法编译。

如果你可以使用c ++ 17,一个新的特性直接解决了这个问题,称为“constexpr if”,如果constexpr为真,它会使得if的主体被处理(除了有效的语句和语法):

template<typename T>
void handle(T &content){
// Initialization was done here
if constexpr (std::is_same(C1, T))
   handle_C1(content);
else if constexpr (std::is_same(C2, T))
   handle_C2(content);
}

如果您没有c ++ 17编译器(或者即使您这样做!),您应该考虑回到原始设计并简单地从每个函数中分解初始化并使其成为通用帮助器功能

答案 2 :(得分:2)

我认为在你的情况下,重载就是你所需要的。只需编写另一种方法,为您进行特殊初始化,处理每个类并在模板化版本(online)中执行常规操作:

void handleSpecial(C1& c1) {
    std::cout << "Handling C1\n";
}
void handleSpecial(C2& c2) {
    std::cout << "Handling C2\n";
}
template <typename T>
void handle(T& content) {
    std::cout << "Doing generell stuff\n";
    handleSpecial(content);
}

SFINAE仅在选择要调用的函数之前有效,在这种情况下不会发生,因为可以使用任何类型调用该函数。然后将生成整个身体并且您得到编译器错误,因为handle_C1没有函数C2,反之亦然。

答案 3 :(得分:1)

完成您的步骤:

如果有多个类在许多方面处理相同但具有单独的字符,那么这可能需要一个基类。例如。你可以有类似的东西:

class CBase { ... common features of C1, C2, C3, ... 
    public:
        ~CBase() {};
        virtual void handle() = 0;
};

class C1 : public CBase { ... something specific to C1
    public:
        virtual void handle() { do what has to be done for C1 }
};
class C2 : public CBase { ... something specific to C2
    public:
        virtual void handle() { do what has to be done for C2 }
};

现在,听起来有点像已经解决了这个问题,因为你可以调用C1.handle()或CBase-&gt; handle()。如果你需要通过外部函数完成它,你可以这样做:

void handle(CBase *base_ptr) {
    base_ptr->handle();
}

就个人而言,我发现这比通过非const引用更好,但我知道这是一个很大的争论。

关于您的SFINAE: SFINAE只是说如果模板替换失败,编译器将继续寻找匹配而不是直接抛出错误。但是如果根本找不到匹配则会抛出错误。在您的情况下,如果是T = C2,它仍然会尝试编译handle_C1(C2),这会失败。 (您的“if case”是运行时决策,而编译器在编译时做出这些决定)

答案 4 :(得分:0)

您可以恢复到良好的旧指针别名。允许将指向类型的指针强制转换为指向另一个类型的指针,如果指向的值没有正确的类型,则只需要UB来解除指针。

但是在这里你知道在运行时类型是正确的,因为std::is_same保证它。所以我会写:

template<typename T>
void handle(T &content){

    // Initialization was done here
    if (std::is_same<C1, T>()) {
        C1* c = reinterpret_cast<C1*>(&content);
       handle_C1(*c);
    }
    if (std::is_same<C2, T>()) {
        C2* c = reinterpret_cast<C2*>(&content);
       handle_C2(*c);
    }
}

它只需花费一个自动指针分配和复制,一个非常聪明的优化编译器可以避免,因为在低级机器代码它显然是无操作。

答案 5 :(得分:0)

这是另一种解决方案,它只是将两种行为共有的代码提取到自己的函数中:

class C1 { /* ... */ };
class C2 { /* ... */ };

template<typename T>
void handle_init(T& content) {
    // Common initialization code
}

void handle(C1& c1) {
    handle_init(c1);
    // C1-specific code
}

void handle(C2& c2) {
    handle_init(c2);
    // C2-specific code
}

根据handle_init内的代码,例如,如果函数的主体只调用&#34; getter&#34;那么模板可能不是必需的。 content的方法,可以事先调用并通过handle_init函数传递给handle

我将两个函数handle命名为与您提供的代码一致,但您可以为它们指定不同的名称,它仍适用于大多数用例。对于大多数重载使用都是这种情况,因为重载解析使用参数的编译时类型,而您作为程序员通常在调用站点知道这一点。

一个很大的例外是handle的参数是模板参数,在这种情况下你需要使用重载(所以其他一些答案实际上确实需要重载因为这个) 。请注意,要根据参数的运行时类型调用不同的函数,重载不起作用,并且您需要像Cedric的答案中那样的虚拟方法。

但除了上面的段落之外,在一个处理不同类型的参数的函数和每个参数类型具有不同函数之间,功能上并没有真正的区别。当然,也许在您的问题域中,将其视为前者是有意义的。并且使用重载调用两个函数handle将提供符号方便性。我认为在做这件事时有意识地意识到这一点是件好事,以防万一发现重载并不具备你需要的功能。

相关问题