c++ - 这个 C++ 代码异常安全吗?

标签 c++ exception

我有来自 mysql.com 的以下代码:

/* Copyright 2008, 2010, Oracle and/or its affiliates. All rights reserved.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.

There are special exceptions to the terms and conditions of the GPL
as it is applied to this software. View the full text of the
exception in file EXCEPTIONS-CONNECTOR-C++ in the directory of this
software distribution.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

/* Standard C++ includes */
#include <stdlib.h>
#include <iostream>

/*
  Include directly the different
  headers from cppconn/ and mysql_driver.h + mysql_util.h
  (and mysql_connection.h). This will reduce your build time!
*/
#include "mysql_connection.h"

#include <cppconn/driver.h>
#include <cppconn/exception.h>
#include <cppconn/resultset.h>
#include <cppconn/statement.h>

using namespace std;

int main(void)
{
cout << endl;
cout << "Running 'SELECT 'Hello World!' AS _message'..." << endl;

try {
  sql::Driver *driver;
  sql::Connection *con;
  sql::Statement *stmt;
  sql::ResultSet *res;

  /* Create a connection */
  driver = get_driver_instance();
  con = driver->connect("tcp://127.0.0.1:3306", "root", "root");
  /* Connect to the MySQL test database */
  con->setSchema("test");

  stmt = con->createStatement();
  res = stmt->executeQuery("SELECT 'Hello World!' AS _message");
  while (res->next()) {
    cout << "\t... MySQL replies: ";
    /* Access column data by alias or column name */
    cout << res->getString("_message") << endl;
    cout << "\t... MySQL says it again: ";
    /* Access column data by numeric offset, 1 is the first column */
    cout << res->getString(1) << endl;
  }
  delete res;
  delete stmt;
  delete con;

} catch (sql::SQLException &e) {
  cout << "# ERR: SQLException in " << __FILE__;
  cout << "(" << __FUNCTION__ << ") on line " << __LINE__ << endl;
  cout << "# ERR: " << e.what();
  cout << " (MySQL error code: " << e.getErrorCode();
  cout << ", SQLState: " << e.getSQLState() << " )" << endl;
}

cout << endl;

return EXIT_SUCCESS;
}

我一直认为这样的代码不是异常安全的,因为 delete 语句不会在异常情况下执行,因此 unique_ptr 或其他一些智能应该使用指针。但由于这是官方代码示例,我对此不确定。所以:

上面的代码示例异常安全吗?

最佳答案

Is the above code sample exception safe?

H**l 不!

这和你说的一模一样。您显示的代码在异常情况下像筛子一样泄漏。

应使用 RAII 或 std::unique 指针。

即使对于准标准 c++ 代码,也有很好的 ole std::auto_ptr


But since this is an official code sample, I am not sure about this.

好吧,开源并不一定意味着它是任何“官方”,或者无论如何“更好”的封闭源代码,其中开发人员对客户隐藏眼睛。

就这些。


好吧,公平地说(对于 MySQL 贡献者):

这是一个最小的使用示例,当 main() 函数 block 退出并且进程终止时,任何内存泄漏都会被清除。

但不是真正的 gem 好例子。考虑到很多平庸的程序员都在那里,并靠 “来自 teh samplez 和 tuz 的 CTRL-C CTRL-V codez” 谋生,我不太确定这是一个好策略。


结论:

不要盲目地复制和接管示例代码,即使不是来自库、工具的官方文档页面或一字不漏的示例代码。

这些都是为了简化,并且可能会集中在图书馆功能的使用上,而不会分散读者的注意力。

他们相当让您负责正确使用他们的东西。


我会做些什么来修复它:

我会考虑提供一个基于拥有和共享指针的薄包装层作为简单的类成员(参见 Dynamic memory management)和 lambda 函数:

class MySQLQuery;

class MySQLConnection {
    std::function<void(void*)> driverCleanup_;
    std::function<void>(void*) connectionCleanup_;
    std::unique_ptr<sql::Driver> driver_;
    std::shared_ptr<sql::Connection> con_;
public:
    MySQLConnection
        ( const std::string& ipAddress
        , const std::string& user
        , const std::string& password
        ) : 
        driverCleanup_([](void *driver) {
          // (sql::Driver*)driver->cleanup(); ## Whatever necessary
          delete driver;
        }) ,
        connectionCleanup_([](void *connection) {
          // (sql::Connection*)connection->cleanup();
          delete connection;
        }),
        driver_(get_driver_instance(),driverCleanup_),
        con_(std::make_shared(driver_->connect(ipAddress,user,password)
                             ,connectionCleanup_))
    {}

    std::unique_ptr<MySQLQuery> CreateQuery(const std::string& query);
};

class MySQLQuery {
     friend class MySQLConnection;

     std::shared_ptr<sql::Connection> con_;
     std::unique_ptr<sql::Statement stmt_;
     MySQLQuery
       ( std::shared_ptr<sql::Connection> connection
       , const std::string& query
       ) : con_(connection) 
         , stmt_(con_->createStatement()
     {
     }
public:
     // And so on ...
     MySQLResultSet Execute() {
        return stmt_->executeQuery("SELECT 'Hello World!' AS _message");
     }
};

std::unique_ptr<MySQLQuery> MySQLConnection::CreateQuery(const std::string& query) 
{
    return std::make_unique<MySQLQuery>(con_,query);
}

此外,您可能会考虑捕获删除函数代码中的任何异常。尽管删除器*从不(重新)抛出异常。这把你搞砸了。

在 IMO 看来,如果成本低,在所有方向上装饰总是一个好主意。

关于c++ - 这个 C++ 代码异常安全吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48718813/

相关文章:

c++ - 在 Visual Studio 中链接 MTL(矩阵模板库)

C++ 列表弹出和推送数据竞争

c++ - 删除数据文件中的条目 C++

java - 拒绝权限 OPPO_COMPONENT_SAFE

java - 如何强制 Java 抛出算术异常?

c++ - 类模板名称中隐藏的 friend 与内联命名空间中的另一个符号冲突

c++ - 将标准输出重定向到整数

c# - 了解 : First Chance exception of type 'Exception Name' occurred in 'dll Name'

java - 如何处理在 try-catch 中抛出普通异常的方法

ruby-on-rails - 注册#create 中发生 ActiveRecord::RecordNotUnique 为什么?