Странное поведение при передаче кабеля перехватывает `std::initializer_list<std::shared_ptr<Conversation>>` в полный обработчик

Приведенный ниже фрагмент кода всегда закрывает соединение сразу, когда клиент подключается к серверу, что совершенно не соответствует моим ожиданиям.

#include <iostream>
#include <memory>
#include <system_error>
#include "tcp_server.hpp"


TcpServer::TcpServer(asio::io_context& io_context,
      const unsigned int& port):m_acceptor(io_context, tcp::endpoint(tcp::v4(), port))
{
  do_accept();
}

void TcpServer::do_accept()
{
      m_acceptor.async_accept(make_strand(m_acceptor.get_executor()),
      [this](const std::error_code& error, tcp::socket socket)
      {
            if (!error)
            {
              std::make_shared<Conversation>(std::move(socket))->do_start();
            }
            else
            {
                std::cerr << "Error accepting connection: " << error.message() << std::endl;
            }

            // Start accepting the next connection
            do_accept();
      });
}

Conversation::Conversation(tcp::socket socket) : m_socket(std::move(socket))
{ 
  std::cout << "creating conversation:" << this << std::endl;
}

void Conversation::do_read()
{
    std::cout << "do read" << std::endl;
    auto conversation_ptr{shared_from_this()};
     m_socket.async_read_some(asio::buffer(m_data),
        [this, conversation_ptr](std::error_code error, size_t length) {
          if (!error)
          {
              do_write(std::string("reply:") + std::string{m_data.data(), length});
              do_read();
              std::cout << "data read: " << m_data.data() << std::endl;
          }
          else
          {
              std::cout << "error in reading data: " << error.message() << std::endl;
          }
        });
}

void Conversation::do_start()
{
  do_read();
}

void Conversation::do_write(std::string str)
{
  auto conversation_ptr{shared_from_this()};
  m_socket.async_send(asio::buffer(str), [this, conversation_ptr](std::error_code error, size_t length ){
    if (!error)
    {
        std::cout << "data write successfully" << std::endl;
    }
    else
    {
        std::cout << "error in writing data: " << error.message() << std::endl;
    }
  });
}

Conversation::~Conversation()
{
   std::cout << "destroying conversation:" << this << std::endl;
}

int main()
{
  asio::io_context io_context;
  TcpServer server{io_context, 12345};

  io_context.run();
}

Поскольку auto conversation_ptr{shared_from_this()}; создает экземпляр std::initializer_list<std::shared_ptr<Conversation>>, а полный обработчик захватывает conversation_ptr, я думаю, что экземпляр Conversation будет сохраняться при вызове полного обработчика, если какое-то сообщение отправляется от клиента. Однако выходные данные сервера всегда примерно такие: ниже.

./tcp_server 
creating conversation:0x1cad8d0
do read
destroying conversation:0x1cad8d0
error in reading data: Operation aborted.

^C

Между тем, вот вывод telnet ниже, который указывает, что сервер, отличный от клиента, закрывает соединение.

telnet 0.0.0.0 12345
Trying 0.0.0.0...
Connected to 0.0.0.0.
Escape character is '^]'.
Connection closed by foreign host.

Более того, это хорошо работает, если auto conversation_ptr{shared_from_this()}; заменить на auto conversation_ptr(shared_from_this());. Какой сюрприз!

Используемый компилятор — clang++ 3.4.

Ubuntu clang version 3.4-1ubuntu3 (tags/RELEASE_34/final) (based on LLVM 3.4)
Target: x86_64-pc-linux-gnu
Thread model: posix

Используемая библиотека asio — V1.29.0.

#define ASIO_VERSION 102900 // 1.29.0

Платформа — Ubuntu14.04.

Distributor ID: Ubuntu
Description:    Ubuntu 14.04.6 LTS
Release:        14.04
Codename:       trusty

ПРИМЕЧАНИЕ. Согласно этому посту , определение max_align_t было добавлено для компиляции фрагмента кода с помощью clang++ 3.4.

Более полный минимально воспроизводимый пример был бы хорошим улучшением этого поста. Вы просите нас принять на веру множество предположений об определении Conversation.

Sneftel 14.06.2024 10:51

Между прочим, на высоком уровне, по моему мнению, такое жонглирование ссылками-спасителями в функторах для поддержания жизни является плохим замыслом. Настройка и удаление беседы имеет побочные эффекты; он заслуживает явной и целенаправленной жизни, а не возникающей.

Sneftel 14.06.2024 10:53

@Sneftel 1. Поскольку в вышеупомянутом фрагменте кода наблюдается странное поведение, сейчас я не могу воспроизвести его с помощью меньшего кода. 2. Ссылки на спасителей в функторах для поддержания жизни — плохой дизайн. Мы не можем заранее знать, когда соединение закрыто или возникла какая-то ошибка.

John 14.06.2024 11:08

@Джон Он просил не меньший код, а более полный код :)

Fareanor 14.06.2024 11:12

@Fareanor Спасибо за объяснение. Извините за мой плохой английский. Код в SO является полным, поскольку он может воспроизвести проблему, не требуя никакой дополнительной информации или кода. Если я ошибаюсь, пожалуйста, дайте мне знать.

John 14.06.2024 11:35
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
5
82
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

std::initializer_list содержит ссылку на временный массив. Это продленное время жизни, как и у обычной const ссылки, т. е. временная ссылка заканчивает свое время жизни, когда исходная initializer_list заканчивает свое время жизни.

Таким образом, захваченная копия initializer_list становится висящей после вызова лямбды: shared_ptr, на который она ссылалась, уже уничтожен. Причина, по которой auto conversation_ptr(shared_from_this()); работает, заключается в том, что вы захватываете shared_ptr напрямую, а лямбда хранит копию.


Кроме того, auto conversation_ptr{shared_from_this()}; делает std::initializer_list<std::shared_ptr<Conversation>> только до N3922. После этого он становится std::shared_ptr<Conversation>. Начиная с Clang 3.6, вы начали получать это предупреждение:

warning: direct list initialization of a variable with a deduced type will change meaning in a future version of Clang; insert an '=' to avoid a change in behavior [-Wfuture-compat]

И наконец был реализован в Clang 3.8, поэтому я подозреваю, что этот код был написан для более нового компилятора.

std::initializer_list содержит ссылку на временный массив? Я не могу найти такого похожего утверждения здесь и там.

John 14.06.2024 11:47

@Джон en.cppreference.com/w/cpp/language/… (также en.cppreference.com/w/cpp/utility/initializer_list/… говорит, что это прокси-объект, т. е. просто указатель на что-то еще)

Artyer 14.06.2024 11:49

Спасибо за подробное объяснение. Да, там говорится, что каждый элемент резервного массива инициализируется копированием с помощью соответствующего элемента списка инициализаторов, а объект std::initializer_list<E> создается для ссылки на этот массив. Следовательно, std::initializer_list<E> является ссылкой на массив, отличный от ссылок на каждый элемент массива. Например, auto nums = {1,2,3}; создаём std::initializer_list<int> с именем nums, который ссылается на массив выпечки, отличный от элементов. Я прав? Если я ошибаюсь, пожалуйста, дайте мне знать.

John 14.06.2024 13:45

И наконец был реализован в Clang 3.8, поэтому я подозреваю, что этот код был написан для более нового компилятора. Извините, я не могу понять, что вы имеете в виду под текстом, выделенным жирным шрифтом.

John 14.06.2024 14:39

@Джон, я не совсем понимаю, что ты имеешь в виду (поддерживающий массив - это элементы). Другой способ сказать это с std::inititalizer_list<int> nums = {1, 2, 3};, это как const int __temp[] = { 1, 2, 3 }; std::inititalizer_list<int> nums /* nums.data() == __temp; nums.size() == 3 */;

Artyer 14.06.2024 14:54

@John В новом компиляторе auto variable{ E } не является initializer_list, а того же типа, что и E, поэтому у вас не возникнет этой проблемы

Artyer 14.06.2024 14:54

Другие вопросы по теме