从 try 中获取不可复制的对象

Getting an uncopyable object out of a try

我有以下 class:

class ShaderProgram {
    private:
        GLuint shaderProgram;
        std::string getCompilationError(GLuint shader);
        std::string getLinkingError(GLuint shaderProgram);

    public:
        // ! Might throw exception !
        ShaderProgram(const std::string &vertexShaderPath, const std::string &fragmentShaderPath);
        ~ShaderProgram();
        // Delete the copy constructor and assignment operator, you should not copy this class
        ShaderProgram(const ShaderProgram&) = delete;
        ShaderProgram* operator=(const ShaderProgram&) = delete;
        // We do allow move constructor and move assignment
        ShaderProgram(ShaderProgram&& other);
        ShaderProgram& operator=(ShaderProgram&&);
};

由于 GLuint shaderProgram,您不能复制此 class 的实例:这需要恰好删除一次,因此如果复制 ShaderProgram 对象并且其中一个副本损坏,所有副本都无法使用。

现在构造函数可能会抛出异常。例如:如果找不到文件或在 glsl compilation/linking 期间出现错误。我想捕获这些错误,打印一条漂亮的错误消息并退出程序。如果没有错误,我可以继续,但我无法将对象移出 try 范围。我看到的唯一解决方案是使用指针:

ShaderProgram* program;

try {
    program = new ShaderProgram("PATH", "PATH");

} catch (Exception e) {
    // print error
    // Exit the program
}
// code
delete program;

但现在我必须手动调用 delete,除非我真的必须这样做,否则我不想这样做。要解决这个问题,我可以使用 unique_ptr 之类的东西,但我觉得应该有更合适的解决方案。

另一种可能性是将所有使用 ShaderProgram 的代码粘贴到 try 中。 ShaderProgram 寿命很长,因此这将导致非常大的 try 块。这可能会使代码更难阅读。

我觉得应该有一个更合适的解决方案。该解决方案是什么?

为什么不在构造函数中使用 try catch?一般来说,try catch 块应该尽可能地应用于关键点。此外,引用可能未完全构建的 this/objects 通常不是一个好主意,在大多数情况下至少是未定义的行为。

更新,因为您发布了更多信息:

使用 unique_ptr + 工厂方案:

通过将 unique_ptr 转发给它的工厂模式提供您要创建的对象,这样它对于您要使用它的每个上下文都是安全的,并且您不会 运行 出现意外行为稍后的。向您的构造函数添加一个引用参数,以跟踪所有发生的问题,以便工厂方法可以转发此信息。 Return 一个 nullptr 用于您的错误情况,但始终转发获得的错误。由于失败的构造似乎是您的(生产代码)案例的有效场景,因此在此处提供专门的错误信息是完全有效的。

没有 unique_ptrs 的解决方案:

将构造函数的关键部分推迟到一个额外的私有方法并在那里应用 try catch。然后通过失败的 bit/member 跟踪构造有效性。这确保了您的对象在问题的语言层上的有效性,但有一些缺点,因为用户可能会忘记根据使用上下文检查状态。标准 io 库的某些部分使用此模式。

无论如何,我更喜欢 unique_ptr 解决方案。

如果您只想在异常发生时终止,您可以使用立即调用的 lambda 表达式来初始化对象。作为一个简化的例子:

#include<iostream>
#include<cmath>

struct ShaderProgram {
    ShaderProgram(const ShaderProgram&) = delete;
    ShaderProgram() {
        throw 1;
    }
};

int main(){


    ShaderProgram x = [](){
        try {
            return ShaderProgram{};
        } catch (...) {
            std::cout << "exception occured" << std::endl;
            std::terminate();
        }
    }();
}

Live Demo

一种方法是使用 public 'init' 函数,其参数当前传递给构造函数。 在 try-catch 块外创建一个没有参数的空 'ShaderProgram',并在块内用参数调用 'init'。

public:
    ShaderProgram();
    ~ShaderProgram();
    // ! Might throw exception !
    int init(const std::string &vertexShaderPath, const std::string &fragmentShaderPath);