传递 unique_ptr 时调试和发布配置的移动不一致?

Inconsistent move in debug and release configurations while passing unique_ptr around?

所以我得到了一些使用 SFML 库处理一些简单的 tcp 套接字的代码。因此,在使用 SFML 功能的情况下创建套接字,并从函数 returned 作为右值引用。 一个组织函数然后将这个套接字传递给(目前只被存储)并通知它的调用者一个套接字是否被处理。然而,这并不像预期的那样工作。

struct TcpSocket : public ::sf::TcpSocket {};

unique_ptr<TcpSocket>&& TcpListener::nonBlockingNext() 
{
    unique_ptr<TcpSocket> new_socket (new TcpSocket) ;
    listener.setBlocking(false);
    if( listener.accept(*new_socket) == ::sf::Socket::Status::Done) 
    {
        new_socket->setBlocking(false);
        std::cout << "Connection established! " << new_socket.get() << "\n";
        return std::move(new_socket);
    }
    return std::move( unique_ptr<TcpSocket>(nullptr) );
}

bool ConnectionReception::processNextIncoming()
{
    unique_ptr<TcpSocket> new_socket (listener.nonBlockingNext());
    std::cout << " and then " << new_socket.get() << "\n";
    if( !new_socket ) return false;

    processNewTcpConnection( ::std::move(new_socket) );
    return true;
}

前面使用的TcpListener的class在组合中封装了一个sf::TcpListener,简单转发一下用法

我有一个简单的测试,尝试连接。

TEST(test_NetworkConnection, single_connection)
{
    ConnectionReception reception;

    reception.listen( 55555 );
    std::this_thread::sleep_for( 50ms );

    TcpSocket remote_socket;
    remote_socket.connect( "127.0.0.1", 55555 );

    std::this_thread::sleep_for( 10ms );
    EXPECT_TRUE( reception.processNextIncoming() );
}

这个测试在我编译它的两种配置中失败的方式不同。 在调试 (g++ -g3) 中,测试意外失败。

[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from test_NetworkConnection
[ RUN      ] test_NetworkConnection.single_connection
Connection established! 0x6cf7ff0
 and then 0
test\test_NetworkConnection.cpp:24: Failure
Value of: reception.processNextIncoming()
  Actual: false
Expected: true
[  FAILED  ] test_NetworkConnection.single_connection (76 ms)
[----------] 1 test from test_NetworkConnection (78 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (87 ms total)
[  PASSED  ] 0 tests.
[  FAILED  ] 1 test, listed below:
[  FAILED  ] test_NetworkConnection.single_connection

 1 FAILED TEST

调试和输出显示,nonBlockingNext() 的第一个 return,即 return 被侦听器接受的套接字,已经到达,但在随后的外部processNextIncoming 的函数 new_socket 的值不是 set/is nullptr.

在 Release 中,即 g++ -O3 输出显示 promise,但测试本身因段错误而崩溃,似乎在测试拆卸中,可能是在释放套接字时,我通过进一步的输出确定,作为调试在优化代码中不是很富有成果。

[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from test_NetworkConnection
[ RUN      ] test_NetworkConnection.single_connection
Connection established! 0xfe7ff0
 and then 0xfe7ff0

我在调试-g3 编译时进一步注意到,'nonBlockingNext()' 中new_socket 的构造似乎在returning:

之前再次到达
Thread 1 hit Breakpoint 1, network::TcpListener::nonBlockingNext (this=0x640f840)
    at test/../src/NetworkConnection.hpp:40
40          unique_ptr<TcpSocket> new_socket (new TcpSocket) ;
(gdb) n
41          listener.setBlocking(false);
(gdb)
42          if( listener.accept(*new_socket) == ::sf::Socket::Status::Done)
(gdb)
44              new_socket->setBlocking(false);
(gdb)
45              std::cout << "Connection established! " << new_socket.get() << "\n";
(gdb)
Connection established! 0x6526340
46              return std::move(new_socket);
(gdb)
40          unique_ptr<TcpSocket> new_socket (new TcpSocket) ;                <<<<<<--------- here
(gdb)
49      }
(gdb)
network::ConnectionReception::processNextIncoming (this=0x640f840) at test/../src/NetworkConnection.hpp:79
79          std::cout << " and then " << new_socket.get() << "\n";
(gdb)
 and then 0
80          if( !new_socket ) return false;
(gdb)

一个步骤,很可能在发布配置中被优化掉,或者可能只是 gdb 很奇怪。

出了什么问题?我该如何继续并让它发挥作用?我在右值和移动上有什么错误吗?

你在这里有未定义的行为:

unique_ptr<TcpSocket>&& TcpListener::nonBlockingNext() 
{
    unique_ptr<TcpSocket> new_socket (new TcpSocket) ;
    //...
    if( /*...*/) 
    {
        //...
        return std::move(new_socket);
    }
    //...
}

问题是您正在 return 引用局部变量 (new_socket)。不要因为它是一个右值引用而分心——它仍然是一个引用!您应该 return unique_ptr 按值代替。而且,即使 std::move() 您正在 returning 的值是合法的,它充其量是无用的,或者在最坏的情况下会错过优化 - 所以只需要 return new_socket.