Efficiently creating C++11 move and copy aware structs

944 Views Asked by At

I am trying to put together an efficient C++1x struct that can take advantage efficient std::move/copy construct/and assignment operations. These structs fall into 2 basic categories, POD structs and non-POD structs. I have gotten into the habit of writing these structs using boilerplate code but I'm pretty sure that the compiler can do a better job at this than I can and its quite a bit of typing for each class. My question is what is the minimum I can do to take advantage of the defaulted compiler operations. I know for example that as soon as I define one explicit constructor, that suppresses the automatic generation of the move and assignment operators. Also I would like the non POD structs that do not have superclasses to have a default destructor and those that inherit from base classes to have a defaulted virtual destructor. The rules on how to do these operations efficiently have always been a bit confusing to me.

The original version of the PAGE_DATA struct is follows

using PAGE_DATA = struct page_data {
    std::string name;
    std::vector<SCRN_TEXT> elements;
    std::vector<int> jumpTable;
};

I then added boilerplate code to make this data structure move aware and I also added an explicit constructor (without which I am forced to initialize it with aggregate initialization using braces which does not read particularly well).

using PAGE_DATA = struct page_data {
    explicit page_data(const std::string& rName = std::string(), 
        const std::vector<SCRN_TEXT>& rElements = std::vector<SCRN_TEXT>(), 
        const std::vector<int>& rJumpTable = std::vector<int>())
        : name(rName)
        , elements(rElements)
        , jumpTable(rJumpTable)
    {}
    //! Defaulted copy constructor.
    page_data(const page_data&) = default;
    //! Defaulted move constructor.
    page_data(page_data&&) = default;

    //! Non-throwing copy-and-swap idiom unified assignment.
    page_data& operator=(page_data rhs) {
        rhs.swap(*this);
        return *this;
    }

    //! Non-throwing-swap idiom.
    void swap(page_data& rhs) noexcept {
        // enable ADL (not necessary in our case, but good practice)
        using std::swap;
        // swap base members
        // ...
        // swap members here
        swap(name, rhs.name);
        swap(elements, rhs.elements);
        swap(jumpTable, rhs.jumpTable);
    }

    virtual ~page_data() = default;

    std::string name;
    std::vector<SCRN_TEXT> elements;
    std::vector<int> jumpTable;
}; 

The dependent simple POD structs that are called out are shown below:

using COLOR_TYPE = enum color_type
{
    BLACK   = 0x0,
    CYAN    = 0x1,
    RED     = 0x2,
    YELLOW  = 0x3,
    GREEN   = 0x4,
    MAGENTA = 0x5,
    AMBER   = 0x6,
    WHITE   = 0x7
};

using SIZE_TYPE = enum size_type {
    SMALL_CHAR = 0x0,
    BIG_CHAR = 0x1
};

using MODIFY_TYPE = enum modify_type {
    NORMAL      = 0x0,
    UNDER       = 0x2,
    FLASH       = 0x4,
    FLASH_UNDER = 0x6
};

using SCREEN_ATTR = struct screen_attr {
    COLOR_TYPE    color  : 4;
    SIZE_TYPE     size   : 2;
    MODIFY_TYPE   blink  : 4;
    unsigned char unused : 6;
};

using CDU_ROWCOL = struct cdu_rowcol {
    int  v;
    int  h;
};

using SCRN_TEXT = struct scrn_text {
    CDU_ROWCOL loc;
    SCREEN_ATTR  attrib;
    std::string text;
};

After putting together a live coliru demo, I was able to verify that the boiler plate does the right thing,.

int main() {
    std::cout << "assertions work fine" << std::endl;
    static_assert(std::is_copy_constructible<PAGE_DATA>(), "not copy constructible");
    static_assert(std::is_move_constructible<PAGE_DATA>(), "not move constructible");
}
3

There are 3 best solutions below

0
On

I like this answer to my question, "... if you find yourself writing move operators and move constructors it's because you have not sufficiently decomposed the problem."

3
On

Your original structure was already perfectly move constructible. All the extra boilerplate code you wrote is already generated by the compiler when needed.

0
On

My question is what is the minimum I can do to take advantage of the defaulted compiler operations.

The minimum - which is quite the optimal amount - is your original version which takes full advantage of the defaulted operations.

Your page_data::swap is probably slightly more efficient than the generic std::swap and it doesn't prevent the generation of any implicit member function so it may be worth to keep in. However, it's probably only marginally better if at all, so you may want to measure whether it is worth cluttering your class definition.