12

I have the following simple class:

class Source
{
public:
    Source() = default;
    Source(Source const&) = delete;
    Source(Source&&) = default;

    explicit Source(std::string const& fileName)
     : inputStream(fileName), path_(fileName)
    {}

    ~Source() = default;

    auto path() const -> std::string
    {
        return this->path_;
    }

    std::ifstream inputStream;
private:
    std::string path_;
};

auto main(int argc, char* argv[]) -> int
{
    Source source(Source("test.txt"));
    cout << source.path() << "\n";

    return 0;
}

According to cppreference ifstream has a move constructor, but when I try to compile that with MinGW 4.7.2, I get the following error:

..\src\main.cpp:32:46: error: use of deleted function 'cy::Source::Source(cy::Source&&)' In file included from ..\src\main.cpp:10:0: source.hpp:28:5: note: 'cy::Source::Source(cy::Source&&)' is implicitly deleted because the default definition would be ill-formed: source.hpp:28:5: error: use of deleted function 'std::basic_ifstream::basic_ifstream(const std::basic_ifstream&)' c:\mingw\bin../lib/gcc/mingw32/4.7.2/include/c++/fstream:420:11: note: 'std::basic_ifstream::basic_ifstream(const std::basic_ifstream&)' is implicitly deleted because the default definition would be ill-formed: c:\mingw\bin../lib/gcc/mingw32/4.7.2/include/c++/fstream:420:11: error: use of deleted function 'std::basic_istream::basic_istream(const std::basic_istream&)'

Am I doing something wrong? Or the documentation of cppreference is inaccurate? Or GCC 4.7.2 has a bug?

ildjarn
  • 62,044
  • 9
  • 127
  • 211
Rayniery
  • 1,557
  • 2
  • 12
  • 17
  • Move constructor is not deleted, copy-constructor is deleted. – Nawaz Feb 01 '13 at 05:36
  • Try Source source(Source("source.txt")); Even though your current code is equivalent, I believe there is a requirement that the operator= be accessible or something. – Seth Carnegie Feb 01 '13 at 05:36
  • @SethCarnegie, thanks for your tip, but it didn't work either, gcc still says that move constructor is implicitly deleted. – Rayniery Feb 01 '13 at 05:40
  • I don't have access to a computer now but you could check the definition of ifstream in your header files and see if it really has a move ctor. If not, it's a gcc bug.Maybe try moving an ifstream by itself and see if gcc is erroneously not using it when it's a member. – Seth Carnegie Feb 01 '13 at 05:43
  • 8
    It just hasn't been implemented yet, [see here](http://gcc.gnu.org/onlinedocs/libstdc++/manual/status.html). All the streams are `Missing move and swap operations`. – Jesse Good Feb 01 '13 at 05:50
  • Thank you very much @JesseGood. That was something I wasn't expecting that the gcc standard library streams were missing move and swap. I will have to think in a workaround for my classes. – Rayniery Feb 01 '13 at 05:58
  • 1
    Using a `std::unique_ptr` is one workaround. – Jesse Good Feb 01 '13 at 06:01
  • I don't want to heap-allocate a std::ifstream object. I will try to change my design (which is a more challenging task :D), so I don't need a move constructor. If I can't, then I think I will use your suggestion of using std::unique_ptr. – Rayniery Feb 01 '13 at 06:15
  • Why don't you want to heap allocate the stream? If the reason is performance it's unlikely to be a noticable difference over creating the stream itself (getting a file handle is quite expensive). – Grizzly Feb 01 '13 at 08:25
  • If it move was supported, you would get heap allocation anyway to facilitate move. – Johan Lundberg Feb 01 '13 at 12:15
  • @JohanLundberg, what do you mean with if move was supported I would get heap allocation anyway? One thing is where ifstream allocate its internal data and other thing is where I allocate my ifstream object. – Rayniery Feb 01 '13 at 19:34
  • @Rayniery The typical way to support fast move is to hold all data by (smart) pointer. The pointer points to the data (normally on the heap [although you can in principle have memory on the stack reserved for this, with a custom allocator.]) Let's say all data in a stream is held by a single non-movable member variable m_data of type StreamImplType. To make the class movable, we could just use a member std::unique_ptr. Have a look at rule-of-zero: http://flamingdangerzone.com/cxx11/2012/08/15/rule-of-zero.html – Johan Lundberg Feb 01 '13 at 20:41
  • @SethCarnegie: It's true that `Type x = val;` and `Type x(val);` are different, but neither one involves `operator=` at all. (For more, see the many questions on copy-initialization and direct-initialization.) – aschepler Feb 06 '13 at 23:50
  • @aschepler yes I know, I meant that it was the same except something has to be accessible (and non-explicit), turns out it's the copy constructor. That's why I said "or something". – Seth Carnegie Feb 07 '13 at 00:13
  • AFAICS, move-version of copy ctor implemented since GCC 5.0. – Artem Pelenitsyn Oct 08 '15 at 09:27

2 Answers2

12

It turns out that the standard library implementation of GCC has not implemented yet the move and swap operation for the stream classes. See here for detail about the current status of C++11 features in the gcc standard library.

Thanks Jesse Good for giving the information and link.

Community
  • 1
  • 1
Rayniery
  • 1,557
  • 2
  • 12
  • 17
  • 1
    I +1'd the answer because it makes sense, but I couldn't find any reference to it in the referenced document. – Carneiro Apr 02 '14 at 01:17
  • @Carneiro, look more closer, it's there: '27.5 Iostreams base classes, Partial, Missing move and swap operations on basic_ios.' – maxschlepzig May 04 '14 at 11:46
1

I realise this answer is a little bit late but to give an unmoveable class move sematics you could write a very simple wrapper class. For example:

#include <memory>



template <typename T>
class swap_wrapper
{
    //! internal buffer to hold object (on heap)
    std::unique_ptr<T> p_obj;
public:
    //! allows any of objects constructors to be called directly
    template <typename... Args>
    explicit swap_wrapper(Args&&... args)
        :   p_obj(
                     new T( std::forward<Args>(args)... )
                     ) {    }

    //! swaps pointer value of T object is unchanged therefore this 
    //! function puts no requirement on base class.

    //! note p_obj is left as a nullptr so dereferencing will cause
    //! undefined behaviour.
    swap_wrapper (swap_wrapper &&other) noexcept
        : p_obj(nullptr)
    {
        swap(other);
    }

    //! swaps pointer like above,
    //! T object is not touched; just the pointer.
    swap_wrapper &operator = (swap_wrapper &&other) noexcept
    {
        swap(other);
        return *this;
    }

    //! uses swap member function of std:unique_ptr
    void swap(swap_wrapper &other) noexcept
    {
        std::swap(p_obj, other.p_obj);
    }

    //! operators to allow access to stream
    T& operator *() { return *p_obj; }
    T const& operator *() const { return *p_obj; }

    T * operator ->() { return p_obj.get(); }
    T const * operator ->() const { return p_obj.get(); }

};


//! overload of default swap (calls member function swap)
template <typename S>
inline void swap(swap_wrapper<S> &one, swap_wrapper<S> &two) noexcept
{ one.swap(two); }

This wrapper can then be returned from functions, passed as an rvalue parameter, etc..