Utilizzo di std :: move in Constructor con gestione degli errori di valore

0

Im confusd sull'uso di std::move nei costruttori di classi.

Voglio passare un valore a una classe e controllare nel costruttore se viene passato un valore valido. Se non viene generata un'eccezione.

Prima di solito passavo il valore con riferimento const:

#include <string>

class Foo {
public:
    Foo(const std::string& value)
        :m_value{ value }
    {
        if (value == "Foo") {
            throw std::invalid_argument("Invalid");
        }
    }
private:
    std::string m_value;
};

Ora leggo in c ++ moderno è meglio passare per valore e spostare il valore passato nella variabile di classe:

#include <string>

class Foo {
public:
    Foo(std::string value)
        :m_value{ std::move (value) }
    {
        // Now m_value must be used because value is empty if reference
        //if(value == "Foo")  -> Desaster value is moved away = empty
        if (m_value == "Foo") {
            throw std::invalid_argument("Invalid");
        }
    }
private:
    std::string m_value;
};

Fin qui tutto bene posso solo fare riferimento alla variabile di classe per eseguire il mio controllo degli errori. Ora ho iniziato a modernizzare un codebass che ha una gerarchia di classi se un valore è già definito nella classe base. La classe bambino esegue fondamentalmente controlli aggiuntivi sulla variabile e sui lanci.

Ecco un esempio semplificato:

#include <string>

class Bar {
public:
    Bar(std::string value)
        :m_value{std::move(value))}
    {
        // value is "empty" here because its moved into m_value
        if (m_value == "Foo") {
            throw std::invalid_argument("Invalid");
        }
    }

    std::string get_value() const { return m_value; }
    void set_value(const std::string& value)
    {
        m_value = value;
    }
private:
    std::string m_value;
};


// Version 1
// We have to ask several times the base class to get the compare value
class Foo : public Bar {
public:
    Foo(std::string value)
        :Bar{ std::move(value) }
    {
        if (get_value() == "BAR" || 
            get_value() == "Bar" || // perform several checks here
            get_value() == "bar") {
            throw std::invalid_argument("Invalid");
        }
    }
};


// Version 2
// We need to make a copy to check. Doesn't this ruin the purpose of 
// std::move by saving a copy??
class Foo : public Bar {
public:
    Foo(std::string value)
        :Bar{ std::move(value) }
    {
        auto check_value = get_value();

        if (check_value == "BAR" ||
            check_value == "Bar" || // perform several checks here
            check_value == "bar") {
            throw std::invalid_argument("Invalid");
        }
    }
};


// Version 3
// doesn't work except we provide in the base class a default constructor
class Foo : public Bar {
public:
    Foo(std::string value)
        //Error here we need to provide a default constructor
    {
        if (value == "BAR" ||
            value == "Bar" || // perform several checks here
            value == "bar") {
            throw std::invalid_argument("Invalid");
        }

        set_value(std::move(value));
    }
};

Quindi la mia domanda è, quale approccio è il migliore. O come può essere gestito meglio?

    
posta Sandro4912 20.12.2018 - 14:21
fonte

2 risposte

2

Il problema che stai riscontrando è che Bar::get_value() copia impazientemente. Il consiglio è che i parametri dovrebbero essere valori, ma risultati potrebbero essere sensibilmente ancora const & .

class Bar {
public:
    Bar(std::string value)
        :m_value{std::move(value))}
    {
        // value is "empty" here because its moved into m_value
        if (m_value == "Foo") {
            throw std::invalid_argument("Invalid");
        }
    }

    const std::string & get_value() const { return m_value; }
    void set_value(std::string value)
    {
        m_value = std::move(value);
    }
private:
    std::string m_value;
};

// We have to ask several times the base class to get the reference, and the compiler is able notice we don't change it in between
class Foo : public Bar {
public:
    Foo(std::string value)
        :Bar{ std::move(value) }
    {
        // value is "empty" here because its moved into Bar
        if (get_value() == "BAR" || 
            get_value() == "Bar" || // perform several checks here
            get_value() == "bar") {
            throw std::invalid_argument("Invalid");
        }
    }
};
    
risposta data 20.12.2018 - 14:36
fonte
1

Basta fare prima il controllo degli errori:

Bar(std::string value)
: m_value{m_value == "Foo" ? throw std::invalid_argument("Invalid") : std::move(value)}
{}

Rispettivamente, se il controllo degli errori è più complicato, considera di metterlo nella sua funzione:

void check_args(std::string const& s) {
    if (get_value() == "BAR" // perform several checks here
    || get_value() == "Bar"
    || get_value() == "bar")
        throw std::invalid_argument("Invalid");
}

Foo(std::string value)
: Bar{ (check_args(value), std::move(value)) }
{
}

Considera anche due costruttori, accettando rispettivamente un std::string&& (se hai già un temporaneo) o un std::string_view (se non lo fai).
Delegare internamente un'implementazione comune è piuttosto banale.

    
risposta data 21.12.2018 - 11:57
fonte

Leggi altre domande sui tag