带有模板参数的自定义 C++ 异常

问题描述

我正在尝试学习如何使用带有模板参数的自定义 C++ 异常。这是一个我试图编译失败的虚拟程序:

#include <string>
#include <iostream>

template <class T>
class MyException : public std::exception {
    public:
        T error;
        MyException(T err) { error = err; };
};


int main(void) {
    try {
        std::string err = "String error";
        throw MyException<std::string>(err);
        return 0;
    }
    catch (MyException e) {
        std::cout << e << std::endl;
        return 1;
    };
}

这是我得到的错误

<source>: In function 'int main()':
<source>:18:9: error: invalid use of template-name 'MyException' without an argument list
   18 |  catch (MyException e) {
      |         ^~~~~~~~~~~
<source>:18:9: note: class template argument deduction is only available with '-std=c++17' or '-std=gnu++17'
<source>:5:7: note: 'template<class T> class MyException' declared here
    5 | class MyException : public std::exception {
      |       ^~~~~~~~~~~
<source>:19:22: error: 'e' was not declared in this scope
   19 |         std::cout << e << std::endl;
      |                      ^

你能帮我为 C++11 修复它吗?

解决方法

你无法捕捉任何模板!您只能捕获特定类型,因此只能捕获模板的特定实例。所以你的代码应该是这样的(快速修复):

#include <string>
#include <iostream>

template <class T>
class MyException : public std::exception {
    public:
        T error;
        MyException(T err) { error = err; };
};


int main(void) {
    try {
        std::string err = "String error";
        throw MyException<std::string>(err);
        return 0;
    }
    catch (const MyException<std::string>& e) {
        std::cout << e.what() << std::endl;
        return 1;
    };
}

https://godbolt.org/z/P4czdP

如果你需要一些方法来捕获这个模板的所有异常,你需要额外的继承层来为这个模板引入通用的唯一父类型:

#include <string>
#include <iostream>


class MyCommonException : public std::exception
{};

template <class T>
class MyException : public MyCommonException {
    public:
        T error;
        MyException(T err) { error = err; };
};


int main(void) {
    try {
        std::string err = "String error";
        throw MyException<std::string>(err);
        return 0;
    }
    catch (const MyCommonException& e) {
        std::cout << e.what() << std::endl;
        return 1;
    };
}

https://godbolt.org/z/xz8r5a

,

c++ 没有“模板捕获”,你只能捕获像

这样的实际类
catch (MyException<string> e) { ... }

幸运的是 std::exception::what() 是虚拟的,因此您可以通过常量引用捕获 std::exception 并打印 what() 的结果。

catch (std::exception const &e) {
    std::cout << e.what() << std::endl;
    return 1;
};

并覆盖 what() 中的 MyException 以在此处创建正确的错误消息:

template <class T>
class MyException : public std::exception {
    public:
        T error;
        MyException(T err) { error = err; };
       
        const char* what() const noexcept override {
            // apply logic to create error message
        }
};

通过这种方式,您可以捕获从 std::exception 继承的任何内容。

,

我将尝试解决有关 C++ 模板的潜在混淆,而不是直接回答,以期防止更多此类问题出现。

粗略地说,语法 template <...> class MyException ... {}; 不会在程序中引入任何“有形的”(类型或值)。没有类型 MyException。每次遇到特定语法 MyException<...> 时,编译器都会实例化模板中的一个类。但是 MyException<...> 的每个实例对于模板参数的每个组合都是唯一的; MyException<A>MyException<B> 不同,它们也可以称为 BlaPft - 它们之间没有任何共同之处。

那么如何让 MyException<A>MyException<B> 以共同的方式行动?好吧,就像处理两个不相关的类一样 - 使用继承和多态。

// define some common base that is NOT a template
class MyException : public std::exception {
    public:
        // put any common API's here ...
        virtual std::string commonWork() = 0;
        virtual ~MyException() {}
};

// now make each template inherit from the common base ...
template <class T>
class MyExceptionImpl : public MyException {
    public:
        T error;
        MyExceptionImpl(T err) { error = err; }
        std::string commonWork() override { return ""; }
};

现在你可以catch (MyException const& e) { ... }

,

@Marek 已经解释过我们无法在 C++ 中捕获模板。 对于您的自定义消息,您可以像这样打印它们:

#include <string>
#include <iostream>

template <class T>
class MyException : public std::exception {
    public:
        T error;
        MyException(T err) { error = err; };
        template<typename U>
        friend std::ostream& operator <<(std::ostream &,const MyException<U>&);
};

template <typename T>
std::ostream& operator<<( std::ostream& o,const MyException<T>& e) {
   return o<<e.error;
}

int main(void) {
    try {
        std::string err = "String error";
        throw MyException<std::string>(err);
        return 0;
    }
    catch (const MyException<std::string>& e) {
        std::cout << e << std::endl;
        return 1;
    };
}
,

这个答案实际上只是将@Marek R 和@churill 已经提到的事情拼凑在一起。

在评论中你说你

  1. 不想捕捉 std::exception,也不想使用 std::exception::what()
  2. 想要专门打印 error 字段。

这只是使用自定义虚拟函数重新实现 what 功能,但您可以这样做:

#include <string>
#include <iostream>

class MyBaseException : public std::exception {
    public:
        virtual std::ostream& intoStream(std::ostream& stream) const = 0;
};

std::ostream& operator<<(std::ostream& stream,const MyBaseException& e) {
    return e.intoStream(stream);
}

template <typename T>
class MyException : public MyBaseException {
    public:
        T error;
        MyException(T err) { error = err; };
    
        std::ostream& intoStream(std::ostream& stream) const override {
            return stream << error;
        }
};

int main(void) {
    try {
        std::string err = "String error";
        throw MyException<std::string>(err);
        return 0;
    }
    catch (const MyBaseException& e) {
        std::cout << e << std::endl;
        return 1;
    };
}

由于只有类方法可以是虚拟的,如果你想使用 << 操作符,你需要一个单独的虚拟函数,它可以访问 error 字段。