Move constructor and const member variables
Asked Answered
S

5

36

I like the idea of const member variables especially when I wrap C functions into classes. The constructor takes a resource handle (e.g. a file descriptor) that stays valid during the whole object life time and the destructor finally closes it. (That is the idea behind RAII, right?)

But with the C++0x move constructor i run into a problem. Since the destructor is also called on the "unloaded" object i need to prevent the cleanup of the resource handle. Since the member variable is const i have no way to assign the value -1 or INVALID_HANDLE (or equivalent values) to indicate to the destructor that it should not do anything.

Is there a way that the destructor is not called if the state of an object was moved to another object?

Example:

class File
{
public:
    // Kind of "named constructor" or "static factory method"
    static File open(const char *fileName, const char *modes)
    {
        FILE *handle = fopen(fileName, modes);
        return File(handle);
    }

private:
    FILE * const handle;

public:
    File(FILE *handle) : handle(handle)
    {
    }

    ~File()
    {
        fclose(handle);
    }

    File(File &&other) : handle(other.handle)
    {
        // The compiler should not call the destructor of the "other"
        // object.
    }

    File(const File &other) = delete;
    File &operator =(const File &other) = delete;
};
Sanbo answered 11/6, 2011 at 17:25 Comment(0)
M
10

No, there is no way to do this. I would suggest that if you're really attached to the handle variable being const you should have a non-const flag member variable that indicates whether or not destruction should do anything.

Meliorism answered 11/6, 2011 at 17:29 Comment(4)
Your answer is for sure a way to go. But related to C++0x I do not like the style that destructors have to check if desctruction should really occur. Shouldn't they assume the object is fully up and running and now is the point of destruction?Sanbo
@mazatwork: Well, think of it this way. Suppose you had a complicated object that could be in several different states that each required a different set of destructors. Like, for example, there is a cache that may or may not be initialized, or a database connection that may or may not need to be closed. Are you 'not really destructing' when you don't close the database connection that isn't open in your destructor? Of course not. This is basically the same thing. You're still destructing, it's just that the state the object is in doesn't meet much work.Meliorism
Why not let the move constructor do some cleanup (if it is actually necessary) so the destructor is left with the real destruction. In my opinion this would fit much better. Because we speak of the same object a double destruction might not reasonable. Your example with complex objects is a thing i try to avoid using techniques like RAII and DI.Sanbo
@matzawork: Well, that's what your move constructor is doing. Cleaning up your object so the destructor doesn't have any work to do. I don't understand the problem.Meliorism
D
29

This is why you should not declare said member variables const. const member variables usually serve no purpose. If you don't want users to mutate the FILE*, then don't provide them with functions to do that, and if you want to stop yourself from mutating it by accident, then mark your functions const. However, do not make member variables themselves const - because then you run into fun when you start to use move or copy semantics.

Dulse answered 11/6, 2011 at 17:31 Comment(6)
Const member variables are useful like references are. Often you want to hold a value and you know that you won't change it, which might allow some optimizations by the compiler. Marking the methods as const will work only as long as i do not have some mutable variables.Sanbo
It doesn't really permit any useful compiler optimizations, in my experience. And it prevents many very useful semantics.Dulse
Exactly true! I ran into fun because I declared a member of an underlying class as a const member.Robillard
const fields are useful because the compiler can complain if you forget to set them with a constructor.Alcaraz
const things serve same purpose as avoiding global variables or trying to reduce the scope: reducing mental load. When you know the field is the same for the whole object lifetime, you don't need to look it up every time in debugger or while refactoring. Btw: a systems language Rust took better approach in that regard: it has everything constant, and to be able to mutate a variable you have to explicitly declare it as mut. Kind of sad that C++ only allows mut in classes, so you can't imitate such behavior by declaring a ton of const aliases :cOkinawa
This strikes me as a language regression. We should not have to sacrifice const semantics of member variables in order to access move semantics.Photodrama
M
10

No, there is no way to do this. I would suggest that if you're really attached to the handle variable being const you should have a non-const flag member variable that indicates whether or not destruction should do anything.

Meliorism answered 11/6, 2011 at 17:29 Comment(4)
Your answer is for sure a way to go. But related to C++0x I do not like the style that destructors have to check if desctruction should really occur. Shouldn't they assume the object is fully up and running and now is the point of destruction?Sanbo
@mazatwork: Well, think of it this way. Suppose you had a complicated object that could be in several different states that each required a different set of destructors. Like, for example, there is a cache that may or may not be initialized, or a database connection that may or may not need to be closed. Are you 'not really destructing' when you don't close the database connection that isn't open in your destructor? Of course not. This is basically the same thing. You're still destructing, it's just that the state the object is in doesn't meet much work.Meliorism
Why not let the move constructor do some cleanup (if it is actually necessary) so the destructor is left with the real destruction. In my opinion this would fit much better. Because we speak of the same object a double destruction might not reasonable. Your example with complex objects is a thing i try to avoid using techniques like RAII and DI.Sanbo
@matzawork: Well, that's what your move constructor is doing. Cleaning up your object so the destructor doesn't have any work to do. I don't understand the problem.Meliorism
K
7

Actually, I've run into this problem myself as well today. Not willing to accept 'can't be done' & 'use shared_ptr / reference counting', googling more, I came up with this base class:

class Resource
{
private:
     mutable bool m_mine;

protected:
    Resource()
    : m_mine( true )
    {
    }

    Resource(const Resource&)       = delete;
    void operator=(const Resource&) = delete;

    Resource(const Resource&& other)
    : m_mine( other.m_mine )
    {
        other.m_mine = false;
    }

    bool isMine() const
    {
        return m_mine;
    }
};

All methods and constructors are protected, you need to inherit from it to use it. Notice the mutable field: this means that descendant can be a const member in a class. E.g.,

class A : protected Resource
{
private:
    const int m_i;

public:
    A()
    : m_i( 0 )
    {
    }

    A( const int i )
    : m_i( i )
    {
    }

    A(const A&& a)
    : Resource( std::move( a     ) )
    , m_i     ( std::move( a.m_i ) ) // this is a move iff member has const move constructor, copy otherwise
    {
    }

    ~A()
    {
        if ( isMine() )
        {
            // Free up resources. Executed only for non-moved objects
            cout << "A destructed" << endl;
        }
    }
};

Field(s) of A can be const now. Note that I've inherited protected, so that user cannot accidentally cast A to Resource (or very willingly to hack it), but A is still not final, so you can still inherit from this (a valid reason to inherit from a Resource is e.g. to have separate read and read-write access). This is one of the extremely rare cases when protected inheritance doesn't automatically mean that your design is faulty; however, if you find it difficult to understand, you might just use public inheritance.

Then, assuming you have a struct X:

struct B
{
    const A m_a;
    const X m_x;

    B(const A&& a, const X& x) // implement this way only if X has copy constructor; otherwise do for 'x' like we do for 'a'
    : m_a( std::move( a ) )
    , m_x(            x   )
    {
    }

    B( const B&& b )
    : m_a( std::move( b.m_a ) )
    , m_x( std::move( b.m_x ) ) // this is a move iff X has move constructor, copy otherwise
    {
    }

    ~B()
    {
        cout << "B destructed" << endl;
    }
};

Note that fields of B can also be const. Our move constructors are const. Given your types have appropriate move constructors, any heap-allocated memory can be shared amongst objects.

Kinin answered 10/6, 2016 at 19:46 Comment(0)
W
4

The typical way to implement a move constructor is to zero out or otherwise invalidate the members of the instance being moved (see MSDN for a simple example). Therefore I would say just don't use const here as it is incompatible with the goals of move semantics.

Willywillynilly answered 11/6, 2011 at 17:30 Comment(1)
Well, you can actually use const and the compiler will still generate the move constructor (of course, if all requirements are satisfied). Here is an example as a prove: codepad.org/Xh4va2eR (ignore the codepad old compiler errors..)Spongioblast
A
-1

Reference counting is standard approach that solves your problem. Consider adding reference counting to your class; either manually, or using existing tools like boost shared_ptr.

Asshur answered 11/6, 2011 at 17:33 Comment(1)
This is an orthogonal topic from the problem being posed (which is handling the moving of Files). What you propose is making shared_ptr<File>s. Could be appropriate, but maybe not, and it certainly would involve a bigger design changeAnhanhalt

© 2022 - 2024 — McMap. All rights reserved.