【问题标题】:Custom C++ exceptions with template arguments带有模板参数的自定义 C++ 异常
【发布时间】:2021-04-20 11:08:57
【问题描述】:

我正在尝试学习如何使用带有模板参数的自定义 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 的问题吗?

【问题讨论】:

    标签: c++ c++11 templates exception


    【解决方案1】:

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

    #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

    【讨论】:

    • 这看起来就像我需要的解决方案。谢谢。但是我想打印“字符串错误”,我应该改变什么来得到它?
    • 实际上,我希望能够在 catch 中使用 error,它是一个通用对象。我想这是不可能的。
    • 有可能。您可以在MyCommonException 中添加一些能够打印信息的虚拟方法。
    【解决方案2】:

    c++ 没有“模板捕获”,您只能捕获实际的类,例如

    catch (MyException<string> e) { ... }
    

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

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

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

    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 继承的任何内容。

    【讨论】:

    • operator &lt;&lt; 重载为MyException 打印自定义消息是个坏主意吗?
    • 这看起来很有趣,但是在一个非虚拟程序中,如果异常是 std::exception 或 MyException,我想做不同的事情。
    • @Medicalphysicist 在这种情况下,您可以使用 Mareks 回答中看到的另一层继承:)
    • @ThePhilomath 我不认为它会有用,因为如果你发现std::exception,你将无法使用这个重载。
    【解决方案3】:

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

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

    那么如何让MyException&lt;A&gt;MyException&lt;B&gt; 以共同的方式行动呢?好吧,就像您对两个不相关的类所做的那样 - 使用继承和多态。

    // 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&amp; e) { ... }

    【讨论】:

      【解决方案4】:

      @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;
          };
      }
      

      【讨论】:

      • 不确定在此处重载operator &lt;&lt; 是否是个坏主意。
      【解决方案5】:

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

      在你说你的cmets中

      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;
          };
      }
      

      由于只有类方法可以是虚拟的,如果要使用&lt;&lt; 运算符,则需要一个单独的虚拟函数,它可以访问error 字段。

      【讨论】:

        猜你喜欢
        • 2021-08-03
        • 1970-01-01
        • 2021-02-17
        • 1970-01-01
        • 2011-12-15
        • 1970-01-01
        • 2014-02-04
        • 2020-12-17
        • 1970-01-01
        相关资源
        最近更新 更多