wake-up-neo.com

Initialisieren Sie statische std :: map mit einem nicht kopierbaren Wert in einer einheitlichen Inline-Initialisierung

Ich möchte einen statischen std::map initialisieren, bei dem der Wert nicht kopierbar ist. Ich werde meine Klasse ValueClass nennen. ValueClass hat einen std::unique_ptr als privates Mitglied, und ich stelle sogar sicher, dass ValueClass nicht kopierbar ist, indem Sie non_copyable erweitern, der wie folgt aussieht:

class non_copyable {
public:
    non_copyable() = default;
protected:
    virtual ~non_copyable() = default;
private:
    non_copyable(const non_copyable&) = delete;
    non_copyable& operator=(const non_copyable&) = delete;
};

Jetzt versuche ich eine std :: map mit meiner Klasse als Wert zu definieren:

static std::map<int, ValueClass> value_classes = {
    {0, ValueClass()},
    {1, ValueClass() }
};

Ich erhalte einen Kompilierungsfehler, da initializer_list versucht, diese Klasse zu kopieren.

Ich habe versucht, an diesem Wochenende über viele Stunden hinweg meine eigene make_map-Funktion zu schreiben, um die Initialisierung ohne Kopieren zu ermöglichen, aber ich bin fehlgeschlagen. Ich habe this , that und other ausprobiert, aber keiner von ihnen kann mit Visual Studio 15.9.4 kompilieren.

Wie kann ich statische std :: map initialisieren, wenn die Kopie nicht erzwungen wird und die Initialisierung in einer Funktion unter Verwendung des Visual Studio-Compilers vereinheitlicht wird?

EDIT: Hier ist die vereinfachte Version des realen Szenarios, in dem ich versuche, dies zum Laufen zu bringen.

#include <iostream>
#include <map>

class non_copyable {
public:
    non_copyable() = default;
protected:
    virtual ~non_copyable() = default;
private:
    non_copyable(const non_copyable&) = delete;
    non_copyable& operator=(const non_copyable&) = delete;
};

class InnerValueClass : public non_copyable
{
public:
    InnerValueClass(const int inner_number) : inner_number_(inner_number) {  }
private:
    int inner_number_;
};

class ValueClass : public non_copyable
{
public:
    ValueClass(const int number1) : number1_(number1) {  }
    ValueClass(const bool condition) : condition_(condition), inner_value_(
        std::make_unique<InnerValueClass>(5)) {  }
private:
    int number1_{};
    bool condition_{};
    std::unique_ptr<InnerValueClass> inner_value_{};
};

/* Inline initialization of std::map copies, this is for initialization of non-copy types*/
template <typename TKey, typename TNonCopyableValue>
class make_map_by_moving
{
    typedef std::map<TKey, TNonCopyableValue> map_type;
    map_type map_;
public:
    make_map_by_moving(const TKey& key, TNonCopyableValue&& val)
    {
        map_.emplace(key, std::move(val));
    }
    make_map_by_moving<TKey, TNonCopyableValue>& operator()(const TKey& key, TNonCopyableValue&& val)
    {
        map_.emplace(key, std::move(val));
        return *this;
    }
    operator const map_type&()
    {
        return map_;
    }
};

static std::map<int, ValueClass> map =
        make_map_by_moving<int, ValueClass>
                (1, ValueClass(5))
                (2, ValueClass(true));
/* It goes on like this for hundreds of lines, so I really appreciate any
solution that leave me with a clean initialization rather than calling
functions on std::map */

int main() { }

Duplicate edit: Die in dieser Frage bereitgestellte Lösung funktioniert nicht mit der Klassenstruktur, die ich habe. Ich suche auch nach einer Lösung, um die make_map_by_moving-Funktion zu korrigieren, mit anderen Worten eine Inline-Initialisierung. Die Antwort gibt eine zwingende Lösung mit Funktionsaufrufen.

8
U. Bulle

Sie können dies nicht direkt tun, da initializer_list für alle Elemente const unterstützt. Diese Elemente müssen aus der Initialisierungsliste in den Container kopiert werden. Das erfordert natürlich das Kopieren. Es gibt leider keine Möglichkeit, von einer Initialisierungsliste zu wechseln.

In C++ 17 können Sie dank der garantierten Kopienauswahl Folgendes tun:

std::map<int, non_copyable> get() {
    std::map<int, non_copyable> m;
    m.emplace(std::piecewise_construct, std::Tuple(0), std::Tuple());
    m.emplace(std::piecewise_construct, std::Tuple(1), std::Tuple());
    return m;
}

std::map<int, non_copyable> value_classes = get();

Dieser Code führt keine Kopien von non_copyable aus. Wir emstruct das Konstrukt innerhalb von map, und dann ist get() ein prvalue. Es gibt keine Kopie/Verschiebung von get() in value_classes. Die m in get()ist das Objekt value_classes.

Ein etwas sneaker-Ansatz wäre, try_emplace() dafür zu missbrauchen:

std::map<int, non_copyable> get() {
    std::map<int, non_copyable> m;
    m.try_emplace(0);
    m.try_emplace(1);
    return m;
}

try_emplace() nimmt den Schlüsseltyp von selbst (so können Sie einfach eine int übergeben) und dann die Argumente für den Wert für das separate Einfügen, was einen viel weniger wortreichen Weg dazu bringt.

6
Barry

Ich denke, Sie müssen das Objekt mit insert_or_assign in einer Funktion erstellen und es dann zurückgeben:

std::map<int, ValueClass> populate()
{
    std::map<int, ValueClass> value_classes;
    value_classes.insert_or_assign(std::make_pair(0, ValueClass());
    return value_classes;
}

Und Ihre Initialisierung wird zu:

std::map<int, ValueClass> value_classes = populate();

Diese Klasse hat jedoch einen virtuellen Destruktor, was bedeutet, dass Sie tatsächlich einen std::map<int, std::unique_ptr<ValueClass>> und nicht eine Karte der tatsächlichen Objekte (nicht wissen, wofür diese Objekte verwendet werden sollen) sein soll.

Bearbeiten Sie nach der Bearbeitung der Frage:

In diesem Fall Barrys suggestion is the one to follow, usingemplace`:

std::map<int, ValueClass> populate()
{
    std::map<int, ValueClass> value_classes;
    value_classes.emplace(1, 5);
    return value_classes;
}

Fügen Sie auch functional hinzu.

4

Sie können einfach initializer_list nicht für move ein Objekt aus einem non-copyable-Objekt verwenden. 

Ihre Klasse löscht den copy constructor & assignment operator. Wenn Sie versuchen, Ihre map oder eine andere container mit einem initializer_list zu initialisieren, zwingt der initializer_list Sie streng, auf eine LValue zu verweisen, und verbietet RValue die Semantik verschieben oder vorwärts. 

Hier ist ein sehr schöner Blogartikel, der alle Details erläutert: knatten.org sowie eine ähnliche Frage/Antwort hier .

2
Francis Cugler