【问题标题】:C++ difference between function declaration and object initializationC++函数声明和对象初始化的区别
【发布时间】:2012-11-23 22:19:04
【问题描述】:

如果我写了这行代码:

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。

【问题讨论】:

  • 查找“最令人头疼的解析”。
  • std::thread t((EchoServer(socket)));?
  • std::thread t{EchoServer(socket)};? (更多 c++11-ish)
  • 我预计这是因为std::thread 没有可用的构造函数采用class EchoServer 类型的参数。
  • This question 是我们关于此事的常见问题解答条目,尽管我对是否将其作为副本关闭有点矛盾。

标签: c++ c++11


【解决方案1】:

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

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

大括号初始化是明确的初始化,在最后一行你有一个带括号的表达式,它不能作为函数参数声明。

【讨论】:

    【解决方案2】:

    正如您所提到的,该语句有两种可能的解释,但标准明确规定,在这种歧义的情况下,编译器必须将该语句解释为函数定义(删除额外的括号集):

    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 功能)

    【讨论】:

    • 第三个 sn-p 不仅仅是一种不同的初始化语法,它完全是一种不同的初始化。
    • @BenVoigt:这三个中的每一个都略有不同的初始化,在这种特殊情况下,它们相当于同一件事。第一个是直接初始化,只需要一个接受EchoServer的构造函数,第二个是copy-initialization。 §8.5/15 实际上使第三种情况与第一种情况相同,并将其命名为direct-initialization,尽管在​​ §12.6.2/2 中指出这对于类类型的对象,语法将具有 list-initialization 语义,特别是 direct-list-initialization
    • [...] 语义与 direct-initialization 相同,除非该类型具有 initializer-list-constructor(其第一个参数是std::initializer_list&lt;&gt;,没有其他参数没有默认值)。由于std::thread 中的情况并非如此,因此在这种情况下,direct-list-initialization 等效于 direct-initialization(即将调用相同的构造函数)。这个详细的描述是否适合答案是一个不同的问题。我最初认为这超出了范围,但在这里:)
    • 我更担心 copy-initialization 的情况,它有额外的要求。它在这里工作是因为std::thread 确实有一个移动构造函数。
    【解决方案3】:

    在 C++ 2011 中,给定情况下最简单的方法是用大括号替换括号:

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

    但是请注意,这保证会调用std::terminate(),因为线程既没有分离也没有加入。

    【讨论】:

    • 问题出在这。我无法调用 t.detach()
    • 总的来说,我认为您实际上并不想想要致电detach(),因为这只是意味着您有一个无法正确清理的流氓线程. ...但这是一个单独的问题;)
    【解决方案4】:

    static_cast可以用来触发自定义转换,试试吧

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

    而不是强制转换的构造函数调用语法。

    要修复转换失败,请将您的构造函数更改为:

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

    【讨论】:

    • socket 不是 EchoServer 的同一类型所以无法编译
    • @elvis:你试过了吗?它不需要是相同的类型,它只需要是可转换的......这也是您尝试编写的代码所必需的。
    • 将近 9 年后阅读我自己的帖子,真是太酷了:D! @BenVoigt 我接受了回复,这是最好的解决方案:D
    【解决方案5】:

    C 和 C++ 编译器必须在解析时构建关于声明和范围的语义知识,并查阅该知识以了解如何解析某些内容。

    当一个标识符被扫描时,它可以被转换为一个标记,其词法类别基于该标识符相对于扫描发生的范围是如何声明的。

    一个比你更简单的例子是:

    A ( B );
    

    这可能是一个函数调用:函数A 正在被调用,其参数是主表达式B 的值。或者,它可能是名称 B 的声明,它是 A 类型的对象。

    如果我们的词法分析器能够查看在当前范围内可见的声明,它可以确定 A 是否是类型名称,或者它是否被声明为函数。然后,它可以将适当种类的标记传递给解析器,从而匹配适当的短语结构规则。

    【讨论】:

      猜你喜欢
      • 2019-06-24
      • 1970-01-01
      • 2015-07-24
      • 1970-01-01
      • 2013-02-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多