CLI11 icon indicating copy to clipboard operation
CLI11 copied to clipboard

Add a move constructor (may not work)

Open ghost opened this issue 6 years ago • 10 comments

So I'm trying to return my app from a function instead of doing everything in main for testing purposes. But when I return the CLI::App instance from the function, I get an error.

Steps to reproduce:

#include <CLI/CLI.hpp>

CLI::App get_app() {
    CLI::App app { "CLI App"};
    return app;
}

int main(int argc, char **argv) {
    auto app = get_app();

    CLI11_PARSE(app, argc, argv);
}

I couldn't find the copy constructor defined anywhere, is that the problem? I know when a destructor is custom-defined, the default copy constructor is not included. Please help!

ghost avatar Apr 23 '19 21:04 ghost

I believe that the internal use of unique_ptr for several of the objects prohibits copy construction, and the virtual destructor is needed for the polymorphism in place on App objects. When I have constructed the app in a helper function the most straightforward and cleanest way is to return by shared ptr.

#include <CLI/CLI.hpp>

std::shared_ptr<CLI::App> get_app() {
   auto app=std::make_shared<CLI::App>("CLI App");
    return app;
}

int main(int argc, char **argv) {
    auto app = get_app();

    CLI11_PARSE(*app, argc, argv);
}

phlptp avatar Apr 23 '19 21:04 phlptp

You should also be able to use std::move or guaranteed copy elision (C++17). But yes, copy is not supported (and it would be wasteful/error prone). I don’t think std::move is tested currently.

henryiii avatar Apr 23 '19 22:04 henryiii

C++17 copy elision works, but it is probably too simple for real use: https://wandbox.org/permlink/QwadeQBBtebP1edT

Looks like App needs a move constructor for moves.

henryiii avatar Apr 23 '19 22:04 henryiii

Yes, adding App(App&&) = default; allows this to be moved. TODO.

henryiii avatar Apr 23 '19 22:04 henryiii

move constructors can be problematic with the virtual calls. I am guessing most of the use cases for polymorphism it wouldn't be an issue since there is no additional data added. but you could easily imagine problematic moves from a base class pointer that does something unexpected.

phlptp avatar Apr 23 '19 23:04 phlptp

Do you have an little example or a link to something about the problem? In a little test I couldn't get an issue. Assuming this is a form of slicing, which is a problem with copy constructors, but you can't keep a reference to the original object after a move. And you can't move across types.

Is this the problem: https://wandbox.org/permlink/k33iMM4jItFkmemf ?

I think we are safe as long as CLI11 doesn't internally use moves on pointers to App (which it doesn't currently)?

henryiii avatar Apr 24 '19 06:04 henryiii

The biggest issue I have experienced is usually returning a polymorphic object by value from a function then using it to construct a base class object with a move constructor or move assignment which typically causes the extra data and class information to get discarded. Usually doesn't cause any segmentation fault or any other errors since everything is still valid, just a sometimes unintentional loss of information, which can lead to some tough to track down errors. So as a general rule if the class is used polymorphically on a typical basis I generally don't allow move constructors and if I do they need to be explicit. In the case of CLI it would probably be ok since I don't imagine too many use cases where it would matter much since there are very few virtual functions. but you should make sure the move constructor is explicit at least.

// This file is a "Hello, world!" CLI11 program

#include <iostream>
#include <utility>

struct A
{
    A(int a) : a_var(a) {}
    int a_var;
    virtual ~A() = default;
    A(A&&) = default;
    virtual void print() const
    {
    std::cout<<"A="<<a_var<<'\n';
    }
};

struct B : public A
{
    using A::A;
    B(B&&) = default;
    B(int a, int b) : A(a), b_var(b) {}
    int b_var;
    
    virtual void print() const override
    {
    std::cout<<"B="<<b_var;
    A::print();
    }
};

B generate()
{
    return B(10,11);
}


int main()
{
    auto g1=generate();
    A g2(generate());
    g1.print();
    g2.print();

    return 0;
}

phlptp avatar Apr 24 '19 15:04 phlptp

actually not sure explicit works with default move constructors. didn't seem to do anything in wandbox

phlptp avatar Apr 24 '19 15:04 phlptp

I got undefined behavior when accessing variables inside callbacks when I added the move constructor. :'(

ghost avatar May 01 '19 15:05 ghost

I assume the variables are somewhere that doesn't get destroyed? If they are part of a class or global, how are you keeping them alive? Could you add a small example of what fails?

henryiii avatar May 01 '19 15:05 henryiii