从 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();
}
}();
}
一种方法是使用 public 'init' 函数,其参数当前传递给构造函数。
在 try-catch 块外创建一个没有参数的空 'ShaderProgram',并在块内用参数调用 'init'。
public:
ShaderProgram();
~ShaderProgram();
// ! Might throw exception !
int init(const std::string &vertexShaderPath, const std::string &fragmentShaderPath);
我有以下 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();
}
}();
}
一种方法是使用 public 'init' 函数,其参数当前传递给构造函数。 在 try-catch 块外创建一个没有参数的空 'ShaderProgram',并在块内用参数调用 'init'。
public:
ShaderProgram();
~ShaderProgram();
// ! Might throw exception !
int init(const std::string &vertexShaderPath, const std::string &fragmentShaderPath);