如何避免 C++ 中由 try/catch 引起的共享指针?
How to avoid shared pointers in C++ caused by try/catch?
我使用共享指针,因为变量只能存在于创建它的块中。
int main(void) {
std::shared_ptr<project::log::Log> log;
try {
log = make_shared<annmu::log::Log>("error.log"); // can throw eception
}
catch(std::exception &e) {
std::cout << "Error\n\n";
return 0;
}
}
我想避免共享指针并创建更简单的代码。类似于下面的代码(无效代码)。
int main(void) {
project::log::Log log; // can throw eception
try {
log = project::log::Log("error.log"); // can throw eception
}
catch(std::exception &e) {
std::cout << "Error\n\n";
return 0;
}
}
这是避免共享指针的好方法吗?它是更有效的解决方案吗?在第二种解决方案中,对象被创建了两次。
谢谢您的回答。
我会首先为我的日志记录定义一个接口,这样我就可以用多种方式创建记录器(你将需要它来进行单元测试)。然后通过 const
引用捕获异常。为了能够使用接口的多态性,需要 std::unique_ptr
到该接口(但至少它不是 shared_ptr)。
完整示例:
#include <iostream>
#include <memory>
#include <string>
class log_itf
{
public:
virtual void log(const std::string& str) const = 0; // logging should not modify anything
virtual ~log_itf() = default;
};
class logger :
public log_itf
{
public:
logger(const std::string& /*filename*/)
{
// let this throw if it fails.
// throw std::runtime_error("file not found");
}
void log(const std::string& str) const override
{
std::cout << str << "\n";
}
};
// a logger that does nothing.
class null_logger :
public log_itf
{
public:
void log(const std::string&) const override {}
};
// example of a class that wants to use logging
class class_that_uses_logging
{
public:
explicit class_that_uses_logging(const log_itf& logger) :
m_logger{ logger }
{
m_logger.log("constructor of class that uses logging");
}
private:
const log_itf& m_logger;
};
// helper factory methods that do the typecasting to interface for you
std::unique_ptr<log_itf> create_logger(const std::string& filename)
{
return std::make_unique<logger>(filename);
}
std::unique_ptr<log_itf> create_null_logger()
{
return std::make_unique<null_logger>();
}
int main()
{
try
{
// for production code
auto logger = create_logger("log.txt");
// for test code
// auto logger = create_null_logger();
logger->log("Hello World\n");
// or pass logging to some other class
// this pattern has a name and is called dependency
// injection and is very useful for big projects
// (with unit tests)
class_that_uses_logging object(*logger);
}
catch (const std::exception& e) // always catch by const&
{
std::cout << "error : " << e.what() << "\n";
}
return 0;
}
而不是使用:
std::shared_ptr<>
make_shared
您也可以使用:
std::unique_ptr<>
make_unique
shared_ptr
速度慢且容易出现问题。
在这种情况下,您需要使用 std::unique_ptr<>
,考虑到在存储指向另一个对象的指针的同时获取对象的所有权不需要它。
回想起来,您不需要共享所有权。
最好避免使用共享指针,除非您实际上是在共享指针。您可以使用 unique_ptr 作为替代品。
拥有一个非抛出构造函数并不是一个坏主意,它将对象构造为有效空状态。在构造中处理异常总是比在操作期间处理异常更复杂。复杂的解决方案需要更多的脑力,而脑力在大型程序中是稀缺资源
所以总的来说,我觉得你说的都对。我喜欢将对象视为具有 6 个不同的阶段。
- 分配
- 施工
- 初始化
- Active // 执行有用的功能等
- 毁灭
- 取消分配
对于简单的对象,将其减少到 construction/destruction 非常方便,并且减少了您需要考虑的事情。对于重量级对象,分离每个阶段并使它们单独测试是有意义的。您可以通过这种方式获得更好的错误处理和错误报告(恕我直言)
我使用共享指针,因为变量只能存在于创建它的块中。
int main(void) {
std::shared_ptr<project::log::Log> log;
try {
log = make_shared<annmu::log::Log>("error.log"); // can throw eception
}
catch(std::exception &e) {
std::cout << "Error\n\n";
return 0;
}
}
我想避免共享指针并创建更简单的代码。类似于下面的代码(无效代码)。
int main(void) {
project::log::Log log; // can throw eception
try {
log = project::log::Log("error.log"); // can throw eception
}
catch(std::exception &e) {
std::cout << "Error\n\n";
return 0;
}
}
这是避免共享指针的好方法吗?它是更有效的解决方案吗?在第二种解决方案中,对象被创建了两次。
谢谢您的回答。
我会首先为我的日志记录定义一个接口,这样我就可以用多种方式创建记录器(你将需要它来进行单元测试)。然后通过 const
引用捕获异常。为了能够使用接口的多态性,需要 std::unique_ptr
到该接口(但至少它不是 shared_ptr)。
完整示例:
#include <iostream>
#include <memory>
#include <string>
class log_itf
{
public:
virtual void log(const std::string& str) const = 0; // logging should not modify anything
virtual ~log_itf() = default;
};
class logger :
public log_itf
{
public:
logger(const std::string& /*filename*/)
{
// let this throw if it fails.
// throw std::runtime_error("file not found");
}
void log(const std::string& str) const override
{
std::cout << str << "\n";
}
};
// a logger that does nothing.
class null_logger :
public log_itf
{
public:
void log(const std::string&) const override {}
};
// example of a class that wants to use logging
class class_that_uses_logging
{
public:
explicit class_that_uses_logging(const log_itf& logger) :
m_logger{ logger }
{
m_logger.log("constructor of class that uses logging");
}
private:
const log_itf& m_logger;
};
// helper factory methods that do the typecasting to interface for you
std::unique_ptr<log_itf> create_logger(const std::string& filename)
{
return std::make_unique<logger>(filename);
}
std::unique_ptr<log_itf> create_null_logger()
{
return std::make_unique<null_logger>();
}
int main()
{
try
{
// for production code
auto logger = create_logger("log.txt");
// for test code
// auto logger = create_null_logger();
logger->log("Hello World\n");
// or pass logging to some other class
// this pattern has a name and is called dependency
// injection and is very useful for big projects
// (with unit tests)
class_that_uses_logging object(*logger);
}
catch (const std::exception& e) // always catch by const&
{
std::cout << "error : " << e.what() << "\n";
}
return 0;
}
而不是使用:
std::shared_ptr<>
make_shared
您也可以使用:
std::unique_ptr<>
make_unique
shared_ptr
速度慢且容易出现问题。
在这种情况下,您需要使用 std::unique_ptr<>
,考虑到在存储指向另一个对象的指针的同时获取对象的所有权不需要它。
回想起来,您不需要共享所有权。
最好避免使用共享指针,除非您实际上是在共享指针。您可以使用 unique_ptr 作为替代品。
拥有一个非抛出构造函数并不是一个坏主意,它将对象构造为有效空状态。在构造中处理异常总是比在操作期间处理异常更复杂。复杂的解决方案需要更多的脑力,而脑力在大型程序中是稀缺资源
所以总的来说,我觉得你说的都对。我喜欢将对象视为具有 6 个不同的阶段。
- 分配
- 施工
- 初始化
- Active // 执行有用的功能等
- 毁灭
- 取消分配
对于简单的对象,将其减少到 construction/destruction 非常方便,并且减少了您需要考虑的事情。对于重量级对象,分离每个阶段并使它们单独测试是有意义的。您可以通过这种方式获得更好的错误处理和错误报告(恕我直言)