Хороший способ предотвратить неоптимальное выполнение кода C ++ 03 в C ++ 11?

17

У меня был некоторый код C ++ 03, который реализовал swap для определенных классов, чтобы сделать std::sort (и другие функции) быстрым.

К сожалению для меня, std::sort теперь использует std::move , что означает, что мой код теперь намного медленнее , чем в C ++ 03.

Я знаю, что могу использовать #if __cplusplus >= 201103L для условного определения оператора move-constructor / move-assignment, но мне интересно, есть ли лучший способ, который не использует хаки препроцессора?

(Я бы хотел избежать взломов пропроцессоров, потому что они были бы уродливыми, поскольку я должен не только проверять версии компилятора, такие как _MSC_VER >= 1600 , но и потому, что они не будут хорошо работать с такими инструментами, как LZZ , которые не распознают синтаксис перемещения C ++ 11, но вынуждают меня предварительно обрабатывать код.)

    
задан Mehrdad 22.01.2014 в 21:28
источник
  • Не определяйте каких-либо специальных членов вообще и просто позволяйте им сгенерировать (используя соответствующие строительные блоки)? –  Xeo 22.01.2014 в 21:33
  • грустно, std :: sort не обязана использовать swap, так как это поможет? –  TemplateRex 22.01.2014 в 21:38
  • @Xeo: К сожалению, Visual C ++ не автогенерирует элементы перемещения. –  Mehrdad 22.01.2014 в 21:48
  • @TemplateRex: Я не уверен, есть ли у вас какие-либо предложения о том, что делать? –  Mehrdad 22.01.2014 в 21:50
  • Я думаю, что перемещение может быть реализовано с точки зрения обмена с временным. Это просто взломать, но вы можете предоставить специальные итераторы или обертки, обеспечивающие такую ​​конструкцию / назначение перемещения. (Это может быть сложно для классов, которые не могут быть эффективно построены по умолчанию). –  dyp 22.01.2014 в 22:07
Показать остальные комментарии

2 ответа

10

Кажется, вопрос на самом деле таков: как можно реализовать конструктор перемещения и назначение перемещения с помощью компиляторов C ++ 03?

Простой ответ: они не могут! Тем не менее, простой ответ игнорирует возможность создания чего-то, что является совершенно допустимым кодом C ++ 03 и которое становится конструктором перемещения и назначением перемещения с помощью компилятора C ++ 11. При таком подходе потребуется использовать хакеры препроцессора, но этот бит используется только для создания заголовка, определяющего несколько инструментов, используемых для фактической реализации.

Вот простой заголовочный файл, который успешно компилируется без каких-либо предупреждений с помощью clang и gcc с включенным или отключенным C ++ 11:

// file: movetools.hpp
#ifndef INCLUDED_MOVETOOLS
#define INCLUDED_MOVETOOLS
INCLUDED_MOVETOOLS

namespace mt
{
#if __cplusplus < 201103L
    template <typename T>
    class rvalue_reference {
        T* ptr;
    public:
        rvalue_reference(T& other): ptr(&other) {}
        operator T&() const { return *this->ptr; }
    };
#else
    template <typename T>
    using rvalue_reference = T&&;
#endif

    template <typename T>
    rvalue_reference<T> move(T& obj) {
        return static_cast<rvalue_reference<T> >(obj);
    }
}

#endif

Основная функция состоит в том, чтобы определить шаблон mt::rvalue_reference<T> , который ведет себя как ссылка на rvalue в C ++ 03, и на самом деле является ссылкой на rvalue (т. е. T&& ) для C + +11. Он не вполне справляется с ссылками на rvalue в C ++ 03, но, по крайней мере, позволяет задавать конструкторы перемещения и задавать перемещения, не требуя ссылок на rvalue.

Обратите внимание, что mt::move() просто используется, чтобы позже показать, как rvalue_reference<T> может быть перемещен даже в C ++ 03! Главное, что rvalue_reference<T> - это то, что понимает компилятор C ++ 03, или T&& . Для этого достаточно разумного обозначения необходимо, чтобы компилятор поддерживал шаблоны псевдонимов. Если это не так, можно применить тот же трюк, но используя подходящий вложенный тип соответствующего шаблона класса.

Вот пример использования этого заголовка:

#include "movetools.hpp"
#include <iostream>

class foo
{
public:
    foo() { std::cout << "foo::foo()\n"; }
    foo(foo const&) { std::cout << "foo::foo(const&)\n"; }
    foo(mt::rvalue_reference<foo> other) {
        std::cout << "foo::foo(&&)\n";
        this->swap(other);
    }
    ~foo() { std::cout << "foo::~foo()\n"; }
    foo& operator= (foo const& other) {
        std::cout << "foo::operator=(foo const&)\n";
        foo(other).swap(*this);
        return *this;
    }
    foo& operator= (mt::rvalue_reference<foo> other) {
        std::cout << "foo::operator=(foo&&)\n";
        this->swap(other);
        return *this;
    }

    void swap(foo&) {
        std::cout << "foo::swap(foo&)\n";
    }
};

int main()
{
    foo f0;
    foo f1 = f0;
    foo f2 = mt::move(f0);
    f1 = f2;
    f0 = mt::move(f1);
}

То есть, настоящая бизнес-логика лишена каких-либо взломов препроцессором. Единственный недостаток препроцессора - в заголовке movetools.hpp , с которым не нужно путаться. То есть, я действительно думаю, что он не использует хаки препроцессора для определения фактического конструктора перемещения или назначения перемещения, хотя препроцессор где-то используется. Если вы настаиваете на том, что не хотите использовать макро-хакерство, это можно сделать, указав компилятору посмотреть на разные заголовки, но это деталь реализации movetools.hpp .

    
ответ дан Dietmar Kühl 22.01.2014 в 23:55
  • @dyp: не mt :: move () используется только в примере, чтобы показать, как вызывается конструктор перемещения и оператор перемещения! Дело в том, что для чего-то, имеющего rvalue-ссылки и шаблоны псевдонимов (например, компилятор C ++ 11, без шаблонов псевдонимов, то же самое можно сделать с использованием вложенного типа), функции, использующие mt :: rvalue_reference <T> as параметры фактически принимают T && в качестве параметра. То есть, цель точно определяется вашим вторым комментарием! –  Dietmar Kühl 23.01.2014 в 00:19
  • Я отвлечу этот комментарий, был немного смущен. На самом деле это довольно приятное решение. (Вам даже не нужно предоставлять поддержку псевдо-перемещения в режиме C ++ 03, эти коммеры просто должны компилировать.) –  dyp 23.01.2014 в 00:24
  • Компилятор поддерживает перемещение: он просто не создает генерации движений. –  Yakk - Adam Nevraumont 23.01.2014 в 03:31
  • +1 спасибо :) не идеально, но я действительно не ожидаю, что есть лучший способ, поэтому я рассмотрю это. –  Mehrdad 23.01.2014 в 05:12
  • @Yakk: несколько компилятор с поддержкой C ++ 11 поддерживает перемещение и перемещение. Тем не менее, код предназначен также для компиляции с компилятором C ++ 03, который не понимает ссылки rvalue. Используя подход, который я описал, компилятор C ++ 03 может скомпилировать код, который превращается в конструктор перемещения и назначение с помощью компилятора C ++ 11. –  Dietmar Kühl 23.01.2014 в 21:22
3

Взгляните на Boost.Move . Он имеет эмуляцию хода для c ++ 03. Может быть, это может помочь, но я не взглянул на детали.

    
ответ дан Germán Diago 23.01.2014 в 06:10
  • +1 Да, я видел это раньше, но использование Boost - это чрезмерное поглощение для того, что я хочу здесь. –  Mehrdad 23.01.2014 в 06:32