Internal swap-ing mechanism, All types swap-able and move-able, Respect class initialization

134 views
Skip to first unread message

Christ-Jan Wijtmans

unread,
Mar 15, 2014, 3:39:46 PM3/15/14
to std-pr...@isocpp.org
Hello dear good Sir or Madam,

Some about me:
I have 13 years of experience in C++, i started out at 10-11 years old. C++11 has inspired me to suggest some useful and improving features.

The experience and annoyance:
Currently i am in a huge project of mine and i believe it might reach at least 30 classes.
That is really hard to maintain, especially now that we have rvalue references, which i believe is an ingenious invention to solve the temp objects issue.
However, making sure that so many classes have proper copy operators and constructors and now in addition rvalue reference operators and constructors is just a pain to maintain.
Also i noticed that it boils down to duplicated code, that is basically the same.

To the point(issue):
A proper class that is swap-able and move-able in c++11 looks a lot like this.

#include <iostream>
class
Foo

{
public:
    Foo()
        :ptr(new int(0))
    {
        std::cout << ptr << "()" << std::endl;
    }

    Foo(int val)
        :ptr(new int(val))
    {
        std::cout << ptr << "(" << val << ")" << std::endl;
    }

    Foo(const Foo& other)
        :ptr(new int(other))
    {
        std::cout << ptr << "(&" << other.ptr << ")" << std::endl;
    }

    Foo(Foo&& other)
        :ptr(new int (0))
    {
        std::cout << ptr << "(&&" << other.ptr << ")" << std::endl;
        swap(other);
    }

    ~Foo()
    {
        std::cout << "~" << ptr << std::endl;
        delete ptr;
    }

   
Foo& operator=(Foo other)
    {
        std::cout << ptr << "=" << other.ptr << std::endl;
        return swap(other);
    }

    operator int() const
    {
        return *ptr;
    }

    Foo& swap(Foo& other)
    {
        std::cout << ptr << "~" << other.ptr << std::endl;
        std::swap(ptr, other.ptr);
    }
private:
    int *ptr;
};

    int main()
    {
        Foo a(1);
        Foo b(std::move(a));
        std::cout << a << " " << b << std::endl;

        Foo c(2);
        c = std::move(b);
        std::cout << c << " " << b << std::endl;

        Foo d(3);
        d = b;
        std::cout << d << " " << b << std::endl;
    }
0x25216d0(1)
0x2521720(&&0x25216d0)
0x2521720~0x25216d0
0 1
0x2523b60(2)
0x2567d20(&&0x25216d0)
0x2567d20~0x25216d0
0x2523b60=0x25216d0
0x2523b60~0x25216d0
~0x2523b60
1 0
0x2523b60(3)
0x2568c20(&0x2567d20)
0x2523b60=0x2568c20
0x2523b60~0x2568c20
~0x2523b60
0 0
~0x2568c20
~0x25216d0
~0x2567d20
~0x2521720


As you can see here there is a lot of code that can be copy and pasted to other classes basically.
Why do all this manually, unless in a special case that can be overloaded? I feel like this should be default behavior, like the copy constructor is.

In the current situation we have no guarantee that a class is swap-able or move-able. (Copies instead of swaps)
This guarantee however can make containers a lot more efficient as well  when dealing with large amounts of data that can basically be swapped.
I propose the syntax a~b for, ::operator~(one, two) which is different from ::operator~(one). The ~ symbol kinda looks like a swapping symbol to me.

This code should be the default behavior for all classes, internal types such as int or pointers are all swap-able like classes with the ~ operator.
It does the same thing as the default copy constructor except copying it does swapping.
    Foo& operator~(Foo& other)
    {
x ~ other.x; //int
y ~ other.y;
//int
z ~ other.z; //int
        ptr ~ other.ptr; // ptr to int
bar ~ other.bar; // class Bar
    }

The default copy constructor does not respect initialization, it might work for PODS but not for classes that take care of pointers or other data.

    Foo& operator=(const Foo& other)

    {
        x = other.x;
        y = other.y;
        z = other.z;
        ptr = other.ptr;
bar = other.bar;
        return *this;
    }
    

If we had an internal swap-ing mechanism the default behavior can easily be replaced with this code.
The copy constructor will work both for PODS and classes that manage points or other data and respect data initialization.
    Foo& operator=(Foo other)
    {
        return *this ~ other;
    }
or 
    Foo& operator=(const Foo& other)
    {
        return *this ~ Foo(other);
    }

    Foo& operator=(Foo&& other)
    {
        return *this ~ other;
    }

The move constructor can have default behavior as well.
    Foo(Foo&& other)
    {
Foo::Foo(); // default constructor
        *this ~ other;
    }


Finally if we assume this implemented the original class would look like this and still function the same, in a pointer safe manner.
Also containers can swap the object around efficiently and neither the user code or the STL implementation has to worry about the safety of the pointer or the copying large amounts of data due to the lack of a move constructor or move operator.

class Foo

{
public:
    Foo()
        :ptr(new int(0))
    {
        std::cout << ptr << "()" << std::endl;
    }

    Foo(int val)
        :ptr(new int(val))
    {
        std::cout << ptr << "(" << val << ")" << std::endl;
    }

    Foo(const Foo& other)
        :ptr(new int(other))
    {
        std::cout << ptr << "(&" << other.ptr << ")" << std::endl;
    }

    ~Foo()
    {
        std::cout << "~" << ptr << std::endl;
        delete ptr;
    }

    operator int() const
    {
        return *ptr;
    }
private:
    int *ptr;
};

   
int main()
    {
        Foo a(1);
        Foo b(std::move(a));
        std::cout << a << " " << b << std::endl;

        Foo c(2);
        c ~ b;
        std::cout << c << " " << b << std::endl;

        Foo d(3);
        d = b;
        std::cout << d << " " << b << std::endl;
    }


Christ-Jan Wijtmans

unread,
Mar 16, 2014, 6:55:44 AM3/16/14
to std-pr...@isocpp.org
Hmm i posted the wrong output of the first test code. 
But i believe this makes C++ more RAII friendly compared to the current situation.

Christ-Jan Wijtmans

unread,
Mar 16, 2014, 7:09:31 AM3/16/14
to std-pr...@isocpp.org
I would like to note that the current behavior could stay for structs so that they stay compatible with C.
Also it would keep structs useful for PODs. I would go even so far as to disallow constructors, destructors, virtuals or class members being in structs so that they are guaranteed to be PODs and compatible with C.
Making them differentiated from classes with default copy and move behavior that respect RAII.

Philipp Maximilian Stephani

unread,
Mar 16, 2014, 11:19:57 AM3/16/14
to std-pr...@isocpp.org
A swap operator has already been proposed in http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3746.pdf. Please discuss that proposal first before making a new one.

Philipp Maximilian Stephani

unread,
Mar 16, 2014, 11:21:35 AM3/16/14
to std-pr...@isocpp.org
Such a change (forcing all structs to be POD) would break billions of lines of working code and will therefore not happen.
--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.

Christ-Jan Wijtmans

unread,
Mar 16, 2014, 11:39:12 AM3/16/14
to std-pr...@isocpp.org
Replacing struct with class is not a difficult task.  It is anyway a side suggestion.

--

---
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/40i99Eqtt3I/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.

Christ-Jan Wijtmans

unread,
Mar 16, 2014, 11:40:32 AM3/16/14
to std-pr...@isocpp.org
And anyway i do not believe new C++ standards should be held back by such trivial things, even if you had a 100 structs that will error it only takes a few minutes replacing it with class.

Ville Voutilainen

unread,
Mar 16, 2014, 11:50:32 AM3/16/14
to std-pr...@isocpp.org
On 16 March 2014 17:40, Christ-Jan Wijtmans <cj.wi...@gmail.com> wrote:
> And anyway i do not believe new C++ standards should be held back by such
> trivial things, even if you had a 100 structs that will error it only takes
> a few minutes replacing it with class.


A few minutes? Perhaps with a refactoring tool that includes a C++
implementation.
Changing

struct derived : base {
string data;
};

to a class requires the insertion of 'public' and 'public:' in the
right places. I have no
intention to spend that few minutes changing valid code that would be
broken on a whim by
a sudden desire to make all structs PODs.

ad...@hexadigm.com

unread,
Apr 6, 2014, 8:30:21 AM4/6/14
to std-pr...@isocpp.org
The fact that an official proposal for this has already been submitted is welcome news. I've also thought about this idea as I'm sure many others have. "std::swap()" should probably be deprecated and replaced with a (non-throwing) "swap()" operator of some type, even if it means just adding a built-in "swap()" function that could be invoked on all fundamental types (though some may cringe at this syntax). For classes, the "swap()" operator (or function call) could then be available as a new special function, similar to the other special (class) functions, so it would be implicitly generated as required. The implicit version would simply call the "swap()" operator (or function) on all its data members (WYSIWYG). While there will no doubt have to be a careful examination of all this, to ensure no (subtle/breaking) problems exist, on the surface it seems this would go a long way to solve a lot of existing confusion surrounding the use use of the existing "std::swap()" (which is unnatural and potentially error-prone compared to the above system). For "operator=(Whatever whatever)", maybe we can all finally see the day where the standard (usual) pattern is to pass "whatever" by value as seen (creating a "unifying" assignment operator as it's so often called), and to simply invoke the new (non-throwing) "swap()" operator on that object, including on all base classes. Details TBD of course (but the basic pattern is simple and already widely known).
Reply all
Reply to author
Forward
0 new messages