Как отключить копирование для конструкторов с помощью std::source_location или обойти проблему?

Я пытаюсь добавить инструментарий к широко используемому классу шаблонов в моем продукте. Я сейчас на VS 2019 (16.10.4) с участием /std:c++17. Новая функция - отличное дополнение для задач, в решении которых я заинтересован. Пока и std::experimental::source_locationнедоступны в моем компиляторе, я построил свой собственный на основе этого ответа . Моя цель - внести изменения в конструктор / члены класса в специальные тесты сборки и запуска. Изменения самого класса не влияют на его использование, поэтому остальная часть кода остается прежней.

Все это компилируется и отлично работает - в основном. За исключением того, что я сталкиваюсь с копией, которая почти превосходит цель использования std::source_location. Эта проблема может быть продемонстрирована на gcc -std=c++20 с и без -fno-elide-constructorsтакже. См. Упрощенную версию моего минимального воспроизводимого образца Godbolt .

Мои занятия:

      class MyClass
{
private:
    int m_a = 0;
    std::source_location m_location = std::source_location::current();

public:
    MyClass(std::source_location location = std::source_location::current())
    : m_location(location)
    {
    }
    MyClass(const MyClass &other, std::source_location location = std::source_location::current())
    : m_a(other.m_a)
    , m_location(location)
    {
    }
    MyClass(MyClass &&other, std::source_location location = std::source_location::current())
    : m_a(other.m_a)
    , m_location(location)
    {
    }
};

Использование:

      int main()
{
    MyClass o1;
    MyClass o2(o1);
    MyClass o3(getCopy1());
    MyClass o4(getCopy2());

    std::cout << "o1: " << o1.getLocationInfo() << std::endl;
    std::cout << "o2: " << o2.getLocationInfo() << std::endl;
    std::cout << "o3: " << o3.getLocationInfo() << std::endl;
    std::cout << "o4: " << o4.getLocationInfo() << std::endl;

    return 0;
}

Фактический выход:

      o1: /app/example.cpp(56:13) int main()
o2: /app/example.cpp(57:18) int main()
o3: /app/example.cpp(46:12) MyClass getCopy1()
o4: /app/example.cpp(51:20) MyClass getCopy2()

Ожидаемый результат:

      o1: /app/example.cpp(56:13) int main()
o2: /app/example.cpp(57:18) int main()
o3: /app/example.cpp(58:26) int main()
o4: /app/example.cpp(59:26) int main()

Текущее поведение соответствует тому, что мы видим для конструкторов с завершающими аргументами по умолчанию. См. Образцы царапин .

1 ответ

Вы можете попробовать что-то вроде этого:

      template <int line, std::size_t N, basic_fixed_string<N> file> 
class MyClassImpl {
   public:
      // copy from any instantiation 
      template <int line2, std::size_t N2, basic_fixed_string<N2> file2>
      MyClassImpl(const MyClassImpl<line2, N2, file2>& other) { ... }
      // also move, assignment etc
      ...
};

#define MyClass MyClassImpl<__LINE__, sizeof(__FILE__)-1, __FILE__>

Ты можешь взять basic_fixed_stringот сюда или свернуть свой собственный; также не стесняйтесь использовать свою реализацию std::source_locationвместо старых макросов, если это возможно. Идея состоит в том, чтобы каждое упоминание о MyClassуникальный тип. Параметры шаблона не используются в определении класса, они могут быть любыми, если они уникальны.

Теперь больше не будет копировать элизию, когда вы это сделаете.

      MyClass o3(getCopy1());

потому что типы не совпадают, поэтому нет копии, которую следует исключить.

Конечно, это вообще не работает, если вы используете auto:

      auto o3 = getCopy1(); // still has copy elision

так что это не надежное решение.

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