C++函数声明和对象初始化的区别

4

如果我写下这行代码:

std::thread t(EchoServer(socket));

编译器如何解释这条指令?它可以是一个函数声明,也可以只是一个初始化。下面是代码:

#include <iostream>
#include <thread>
#include <boost/asio.hpp>

#include <boost/asio.hpp>

typedef boost::asio::ip::tcp::socket Socket;
auto socket_deleter = [] (Socket* s) {s->close(); delete s;};
typedef std::unique_ptr<Socket, decltype(socket_deleter)> socket_ptr;

class EchoServer {
public:
    static void Listen(unsigned int port)
    {

        using namespace std;
        using namespace boost::asio;

        io_service ios;

        // create an endpoint to listen to a certain port
        ip::tcp::endpoint endpoint(ip::tcp::v4(), port);

        cout << "Listening to TCP Socket on port " << port << " ..." << endl;

        // Start opening a socket
        ip::tcp::acceptor acceptor(ios, endpoint);

        // this loop must be infinite... but we accept only 3 connections
        auto socket = socket_ptr(new Socket(ios));

        std::thread t(EchoServer(socket));
    }

    EchoServer(socket_ptr&& s) : m_socket(std::move(s))
    {
    }

    void operator ()() const
    {
    }

private:
    socket_ptr m_socket;
};

但编译器给出了以下警告:
C4930: 'std::thread t(EchoServer(socket))': std::thread t(EchoServer(socket)) function not called (was a variable definition intended?).

那么我该如何解释这行代码是std :: thread创建对象而不是函数声明呢?

更新1: 我使用的是不支持统一初始化的Visual Studio 2012,因此我已将代码从std::thread t((EchoServer(socket)));更改为std :: thread t {EchoServer(socket)} ,但这次我遇到了一个编译时错误,我不理解:

error C2440: '<function-style-cast>': cannot convert from 'std::unique_ptr<_Ty,_Dx>' to 'EchoServer'

我错过了什么吗?

更新2 我可能需要更好地理解移动语义,问题出在socket_ptr的声明上。我已经以这种(丑陋的)方式更改了代码...但现在可以编译了。

#include <iostream>
#include <thread>
#include <boost/asio.hpp>

typedef boost::asio::ip::tcp::socket Socket;
auto socket_deleter = [] (Socket* s) {s->close(); delete s;};
/*
typedef std::unique_ptr<Socket, decltype(socket_deleter)> socket_ptr;
*/
typedef Socket* socket_ptr;

class EchoServer {
public:
    static void Listen(unsigned int port)
    {

        using namespace std;
        using namespace boost::asio;

        io_service ios;

        // create an endpoint to listen to a certain port
        ip::tcp::endpoint endpoint(ip::tcp::v4(), port);

        cout << "Listening to TCP Socket on port " << port << " ..." << endl;

        // Start opening a socket
        ip::tcp::acceptor acceptor(ios, endpoint);

        // this loop must be infinite... but we accept only 3 connections
        auto socket = new Socket(ios);

        std::thread t((EchoServer(socket)));
    }

    EchoServer(socket_ptr s) : m_socket(s)
    {
    }

    ~EchoServer()
    {
        m_socket->close();
        delete m_socket;
    }

    void operator ()() const
    {
    }

private:
    socket_ptr m_socket;
};

将socket_ptr作为一个简单指针而不是unique_ptr,代码就能正常工作。

6
请查阅“最令人困扰的解析”。 - Pubby
std::thread t((EchoServer(socket))); - Luchian Grigore
std::thread t{EchoServer(socket)}; (更 C++11 风格的写法) - ipc
我认为这是因为没有可用的构造函数可以接受类型为“class EchoServer”的参数。 - paddy
这个问题是我们关于此事的常见问题解答,尽管我有些矛盾是否将其关闭为重复问题。 - Xeo
5个回答

7

这是一个函数声明。如果你想要使用直接初始化方式声明一个对象,可以使用以下方法之一:

std::thread t(EchoServer { socket });
std::thread t { EchoServer(socket) };
std::thread t { EchoServer { socket} };
std::thread t((EchoServer(socket)));

花括号初始化是明确的初始化,而在最后一行中,您有一个带括号的表达式,无法作为函数参数声明。


5

正如您所提到的,该语句有两种可能的解释,但标准明确规定,在这种模棱两可的情况下,编译器必须将该语句解释为函数定义(去掉多余的括号):

std::thread t(EchoServer socket);

如果您希望强制创建一个std::thread,您可以添加一组额外的括号来使该语句不是一个有效的函数声明:
std::thread t((EchoServer(socket)));

使用不同的语法进行初始化:

std::thread t = std::thread(EchoServer(socket));

如果您使用的是C++11,您可以使用统一初始化:

std::thread t{EchoServer(socket)};

前两个选项是有效的C ++ 03,但在C ++ 11编译器中,您可能应该使用第三个选项(而且使用std :: thread表明您正在使用C ++ 11功能)


1
第三个片段不仅是初始化的不同语法,而且它完全是一种不同的初始化方式。 - Ben Voigt
@BenVoigt:这三种初始化方式略有不同,在这种特殊情况下它们的作用相同。第一种是直接初始化,只需要一个接受EchoServer的构造函数,第二种是复制初始化。实际上,§8.5/15将第三种情况与第一种情况视为相同,并将其命名为直接初始化,尽管在§12.6.2/2中它指出,该语法对于类类型的对象将具有列表初始化语义,特别是直接列表初始化 - David Rodríguez - dribeas
除非类型具有初始化器列表构造函数(第一个参数是std::initializer_list <>且没有其他没有默认值的参数),否则语义直接初始化相同。由于在std :: thread中不是这种情况,因此在这种情况下,直接列表初始化等效于直接初始化(即将调用相同的构造函数)。这个详细的描述是否适合答案是另一个问题。我最初认为这超出了范围,但在这里 :) - David Rodríguez - dribeas
我更担心复制初始化的情况,因为它有额外的要求。这里可以工作是因为std::thread确实有一个移动构造函数。 - Ben Voigt

3
在C++ 2011中,在给定情况下最简单的方法是用大括号替换括号:
std::thread t{EchoServer(socket)};

请注意,由于该线程既没有分离也没有加入,因此这肯定会调用std::terminate()


问题就在这里。我无法调用t.detach()。 - Elvis Dukaj
一般来说,我认为你实际上并不想调用detach(),因为这意味着你有一个无法正确清理的流浪线程。...但这是一个单独的问题 ;) - Dietmar Kühl

1

static_cast 可以用于触发用户定义的转换,因此请尝试使用

std::thread t(static_cast<EchoServer>(socket));

使用强制类型转换时,应该使用构造函数调用语法。

为了解决转换失败的问题,请将您的构造函数更改为:

EchoServer(socket_ptr&& s) : m_socket(s)
{
}

套接字的类型与EchoServer不同,因此无法编译。 - Elvis Dukaj
1
@elvis:你试过了吗?它不需要是相同的类型,只需要可转换即可...这也是你尝试编写的代码所必需的。 - Ben Voigt
阅读自己的帖子,已经将近9年了,感觉真棒:D!@BenVoigt 我接受了你的回复,这是最好的解决方案:D - Elvis Dukaj

1

C和C++编译器在解析时必须建立关于声明和作用域的语义知识,并在解析时咨询该知识,以便知道如何解析某些内容。

当扫描标识符时,可以将其转换为一个令牌,其词汇类别基于该标识符相对于扫描所在范围的声明方式。

比您的例子更简单的示例是:

A ( B );

这可能是一个函数调用:使用主表达式B的值作为参数调用函数A。或者,它可能是名称B的声明,该名称将成为类型A的对象。
如果我们的词法分析器能够查看当前范围内可见的声明,它可以确定A是类型名称还是被声明为函数。然后,它可以将适当类型的标记传递给解析器,因此适当的短语结构规则将匹配。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接