2013-10-27 21 views
9

Ich versuche, ein gutes Design für meine Software zu definieren, die über Lese-/Schreibzugriff auf einige Variablen wobei darauf geachtet, impliziert. Hier habe ich das Programm für die Diskussion vereinfacht. Hoffentlich wird dies auch für andere hilfreich sein. :-)Shared_ptr <T> Shared_ptr <T const> und Vektor Vektor <T><T const>

Lassen Sie uns sagen, dass wir eine Klasse X wie folgt haben:

class X { 
    int x; 
public: 
    X(int y) : x(y) { } 
    void print() const { std::cout << "X::" << x << std::endl; } 
    void foo() { ++x; } 
}; 

Lasst uns auch sagen, dass diese Klasse in der Zukunft mit X1 wird subclassed, X2, ..., die print() und foo() reimplementieren können . (I entfallen die erforderlichen virtual Schlüsselwörter der Einfachheit halber hier, da es nicht das eigentliche Problem, das ich bin vor.)

Da wir polymorphisme verwenden, lassen Sie uns verwenden (smart) Zeiger und definieren eine einfache Fabrik:

using XPtr = std::shared_ptr<X>; 
using ConstXPtr = std::shared_ptr<X const>; 

XPtr createX(int x) { return std::make_shared<X>(x); } 

Bis jetzt ist alles in Ordnung: Ich kann goo(p) definieren, die lesen und schreiben kann und hoo(p), die nur lesen können.

void goo(XPtr p) { 
    p->print(); 
    p->foo(); 
    p->print(); 
} 

void hoo(ConstXPtr p) { 
    p->print(); 
// p->foo(); // ERROR :-) 
} 

Und der Anruf Seite sieht wie folgt aus:

XPtr p = createX(42); 

    goo(p); 
    hoo(p); 

Der gemeinsame Zeiger auf X (XPtr) automatisch in seine const Version umgewandelt wird (ConstXPtr). Schön, genau das will ich!

Jetzt kommen die Probleme: Ich brauche eine heterogene Sammlung von X. Meine Wahl ist eine std::vector<XPtr>. (Es könnte auch ein list sein, warum nicht.)

Das Design, das ich im Sinn habe, ist das Folgende. Ich habe zwei Versionen des Containers: einen mit Lese-/Schreibzugriff auf seine Elemente, einen mit Lesezugriff auf seine Elemente.

using XsPtr = std::vector<XPtr>; 
using ConstXsPtr = std::vector<ConstXPtr>; 

Ich habe eine Klasse bekam, der diese Daten verarbeitet:

class E { 
    XsPtr xs; 
public: 
    E() { 
     for (auto i : { 2, 3, 5, 7, 11, 13 }) { 
      xs.emplace_back(createX(std::move(i))); 
     } 
    } 

    void loo() { 
     std::cout << "\n\nloo()" << std::endl; 
     ioo(toConst(xs)); 

     joo(xs); 

     ioo(toConst(xs)); 
    } 

    void moo() const { 
     std::cout << "\n\nmoo()" << std::endl; 
     ioo(toConst(xs)); 

     joo(xs); // Should not be allowed 

     ioo(toConst(xs)); 
    } 
}; 

Die ioo() und joo() Funktionen sind wie folgt:

void ioo(ConstXsPtr xs) { 
    for (auto p : xs) { 
     p->print(); 
//  p->foo(); // ERROR :-) 
    } 
} 

void joo(XsPtr xs) { 
    for (auto p: xs) { 
     p->foo(); 
    } 
} 

Wie Sie sehen können, in E::loo() und E::moo() Ich muss etwas Umwandlung mit toConst():

machen
ConstXsPtr toConst(XsPtr xs) { 
    ConstXsPtr cxs(xs.size()); 
    std::copy(std::begin(xs), std::end(xs), std::begin(cxs)); 
    return cxs; 
} 

Aber das bedeutet, dass alles immer und immer wieder zu kopieren ....: -/

Auch in moo(), die const ist, kann ich joo() nennen, welche xs ‚s Daten ändern wird. Nicht was ich wollte. Hier würde ich einen Kompilierungsfehler bevorzugen.

Der vollständige Code ist verfügbar unter ideone.com.

Die Frage ist: Ist es möglich, das gleiche zu tun, aber ohne den Vektor auf seine const-Version zu kopieren? Oder, allgemeiner, gibt es eine gute Technik/Muster, die sowohl effizient als auch einfach zu verstehen sind?

Vielen Dank. :-)

+0

Get eine 'const'-Ansicht mit' boost :: adapters :: transformed' und einem passenden ate function-Objekt, um Ihre Shared-Pointer zu konvertieren. – Xeo

+0

@Xeo: Ich habe mich schnell um 'boost :: adapter :: transformed' gekümmert, aber es scheint, dass ich etwas herum kopieren muss, ein bisschen wie oben, aber mit einer anderen Syntax, oder? Wenn es nicht der Fall ist, würden Sie gerne ein Beispiel geben? :-) – Hiura

+0

Nur eine Anmerkung. Dein 'std :: move (i)' wird nichts bewegen. 'move' bewegt sich nicht, es ist nur eine Besetzung. Vielleicht ist es nur vom Kopieren Ihres tatsächlichen Codes, wohin es sich bewegt :) – typ1232

Antwort

0

Basierend auf den Kommentaren und Antworten habe ich am Ende eine Ansichten für Container erstellt.

Grundsätzlich habe ich neue Iteratoren definiert. Ich erstelle ein Projekt auf github hier: mantognini/ContainerView.

Der Code kann wahrscheinlich verbessert werden, aber die Grundidee ist, zwei Vorlagenklassen zu haben, und ViewConstView, auf einen vorhandenen Behälter (z.B. std::vector<T>), die für Iterieren auf den darunterliegenden Behälter, der eine begin() und end() Methode hat.

Mit ein wenig Vererbung (View ist ein ConstView) hilft es, lesen-schreiben mit in schreibgeschützte Ansicht zu konvertieren, wenn benötigt ohne zusätzlichen Code.

Da ich Zeiger nicht mag, habe ich Template-Spezialisierung verwendet, um std::shared_ptr zu verbergen: eine Ansicht auf einen Container von std::shared_ptr<T> wird keine zusätzliche Dereferenzierung erforderlich. (Ich habe es noch nicht für rohe Zeiger umgesetzt, da ich sie nicht verwenden.)

Here is a basic example of my views in action.

6

Ich denke, die übliche Antwort ist, dass für eine Klasse-Vorlage X<T> jede X<const T> spezialisiert sein könnte und damit der Compiler erlauben nicht einfach davon ausgehen, es einen Zeiger oder eine Referenz von X<T>-X<const T> umwandeln kann und dass es nicht allgemein ausgedrückt, dass diese beiden tatsächlich konvertierbar sind. Aber dann tue ich: Warte, es gibt eine Möglichkeit zu sagen X<T>IS AX<const T>. IS A wird über Vererbung ausgedrückt.

Während dies für std::shared_ptr oder Standardcontainer nicht hilft, ist es eine Technik, die Sie möglicherweise verwenden möchten, wenn Sie Ihre eigenen Klassen implementieren. Tatsächlich frage ich mich, ob std::shared_ptr und die Container verbessert werden könnten/sollten, um dies zu unterstützen. Kann jemand irgendein Problem damit sehen?

Die Technik, die ich im Sinn haben würde wie folgt funktionieren:

template< typename T > struct my_ptr : my_ptr< const T > 
{ 
    using my_ptr< const T >::my_ptr; 
    T& operator*() const { return *this->p_; } 
}; 

template< typename T > struct my_ptr< const T > 
{ 
protected: 
    T* p_; 

public: 
    explicit my_ptr(T* p) 
     : p_(p) 
    { 
    } 

    // just to test nothing is copied 
    my_ptr(const my_ptr& p) = delete; 

    ~my_ptr() 
    { 
     delete p_; 
    } 

    const T& operator*() const { return *p_; } 
}; 

Live example

+0

Haben Sie versucht, das zu kompilieren? –

+0

@typical Ja, siehe den Link "Live Beispiel" in der Antwort und fühlen Sie sich frei, damit zu spielen. –

+1

'std :: shared_ptr ' kann * über * auf seine const Version konvertiert 'template < class Y > shared_ptr (const Shared_ptr & r);'. Das ist, was mit 'Schmiere im ersten Teil meiner Frage geschieht()' und 'hoo (Ich denke, ich muss das Rad für die geteilte Zeigerklasse nicht neu erstellen, nicht wahr? – Hiura

1

Es ist ein grundlegendes Problem mit dem, was Sie tun möchten.

A std::vector<T const*> ist keine Einschränkung eines std::vector<T*>, und das gleiche gilt für vector s intelligente Zeiger und ihre const Versionen enthalten.

Konkret kann ich im ersten Container einen Zeiger auf const int foo = 7; speichern, aber nicht den zweiten. std::vector ist sowohl eine Reihe als auch ein Container. Es ist ähnlich dem T** vs T const** Problem.

Jetzt ist technisch std::vector<T const*> const eine Beschränkung von std::vector<T>, aber das wird nicht unterstützt.

Ein Weg um dies zu starten, ist die Arbeit mit Range Views: keine Ansichten in andere Container zu besitzen. Ein nicht besitzender T const* Iterator-Blick in eine std::vector<T *> ist möglich, und kann Ihnen die gewünschte Schnittstelle geben.

boost::range kann das Boilerplate für Sie tun, aber Ihre eigenen contiguous_range_view<T> oder random_range_view<RandomAccessIterator> schreiben ist nicht schwer. Es wird schick, wenn Sie die Iterator-Kategorie automatisch erkennen und Fähigkeiten aktivieren möchten, die darauf basieren, weshalb boost::range viel mehr Code enthält.

+0

Könntest du ein wenig über diese boost :: range erzählen? Vielleicht, indem Sie zeigen, wie ioo und joo geschrieben werden sollten, um diese zu verwenden und wie die Call-Site aussehen würde? Vielen Dank. – Hiura

+0

Der 'std :: vector ' Ersatz wäre etwas wie 'boost :: iterator_range :: const_iterator>', wenn ich die 'boost' Dokumente richtig lese. – Yakk

1

Hiura,

Ich habe versucht, den Code von Repo- und g zu kompilieren ++ 4.8 gab einige Fehler zurück. Änderungen in main.cpp: 97 und die restlichen Zeilen Aufruf von view :: create() mit Lambda-Funktion als zweites Argument. + add +

auto f_lambda([](view::ConstRef_t<view::ElementType_t<Element>> const& e) { return ((e.getX() % 2) == 0); }); 

std::function<bool(view::ConstRef_t<view::ElementType_t<Element>>)> f(std::cref(f_lambda)); 

+ mod +

printDocument(view::create(xs, f)); 

auch View.hpp: 185 weitere Betreiber erforderlich, nämlich: + add +

bool operator==(IteratorBase const& a, IteratorBase const& b) 
{ 
    return a.self == b.self; 
} 

BR, Marek Szews

+0

Vielen Dank für die Aufmerksamkeit, aber Sie sollten ein Problem auf Github öffnen, anstatt hier zu antworten - wenn Sie dem Geist der SO folgen wollen. – Hiura

Verwandte Themen