Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Good way to prevent C++03 code from performing suboptimally in C++11?

I had some C++03 code that implemented swap for certain classes, to make std::sort (and other functions) fast.

Unfortunately for me, std::sort now seems to use std::move, which means my code is now much slower than it was in C++03.

I know I can use #if __cplusplus >= 201103L to conditionally define a move-constructor/move-assignment operator, but I'm wondering if there's a better way that doesn't use preprocessor hacks?

(I'd like to avoid the proprocessor hacks because they would be ugly, since not only do I have to test for compiler versions like _MSC_VER >= 1600, but also because they wouldn't work well with tools like LZZ that don't recognize C++11 move syntax but force me to preprocess the code.)

like image 341
user541686 Avatar asked Jan 22 '14 20:01

user541686


2 Answers

It seems the question really is: how can a move constructor and move assignment be implemented with a C++03 compilers?

The simple answer is: they can't! However, the simple answer neglects the possibility of creating something which is perfectly valid C++03 code and which become move constructor and move assignment with a C++11 compiler. This approach will need to use some preprocessor hackery but that bit is only used to create a header defining a few tools used for the actual implementation.

Here is a simple header file which happily compiles without any warnings with clang and gcc with C++11 enabled or disabled:

// 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

The basic feature is to define a template mt::rvalue_reference<T> which behaves somewhat like an rvalue reference in C++03 and actually is an rvalue reference (i.e., a T&&) for C++11. It won't quite deal with C++03 rvalue references but, at least, allows to have move constructors and move assignments defined without actually needing rvalue references.

Note that mt::move() is just used to later show how rvalue_reference<T> can be moved even in C++03! The main point is that rvalue_reference<T> is either something a C++03 compiler understands or T&&. For this quite reasonable notation it is necessary that the compiler support alias templates. If that isn't the case, the same trick can be applied but using a suitable nested type of a corresponding class template.

Here is an example use of this header:

#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);
}

That is, the actual business logic is devoid of any preprocessor hackery. The only need to faff about with the preprocessor is in the header movetools.hpp which doesn't need to be messed with. That is, I actually think it does not use preprocessor hacks to define the actual move constructor or move assignment although the preprocessor is used somewhere. If you insist that you don't want to use macro hackery, it can be done by directing the compiler to look at different headers but that's an implementation detail of the movetools.hpp.

like image 67
Dietmar Kühl Avatar answered Nov 16 '22 16:11

Dietmar Kühl


Take a look at Boost.Move. It has move emulation for c++03. Maybe it can help, but I didn't take a look at the details.

like image 39
Germán Diago Avatar answered Nov 16 '22 17:11

Germán Diago