2017-04-12 51 views
3

我有一個庫存類,我想在這個類中使用劍,盾和藥水類的對象。繼承類的數組

class Inventory { 
public: 
    Inventory(); 
    ~Inventory(); 
    virtual void add(); 
    Inventory** getinvent(); 
    void setinvent(Inventory** new_inventory); 
    int getsize(); 
    void setsize(int new_size); 
private: 
    Inventory** inventory; 
    int invent_size; 
}; 

Inventory::Inventory() { 
    inventory = new Inventory*[1]; 
    invent_size = 1; 
} 

class Sword : public Inventory { 
public: 
    Sword(int strength); 
    ~Sword(); 
    void add(); 
private: 
    int strength; 
    Sword* sword; 
}; 

Sword::Sword(int strength) { 
    this->strength = strength; 
    sword = this; 
} 

void Sword::add() { 
    setsize(getsize() + 1); 

    Inventory** new_invent = new Inventory*[getsize()]; 
    for (int i = 0; i < getsize() - 1; i++) { 
     new_invent[i] = getinvent()[i]; 
    } 

    new_invent[getsize() - 1] = sword; 
    setinvent(new_invent); 
} 

護盾和藥水類與劍類相似。如果我在執行中提出以下幾個對象:

Inventory* inventory = new Inventory(); 
Sword* sword = new Sword(1); 

我該如何將此劍添加到此特定庫存?我不認爲劍 - >加();將工作,因爲劍不知道它是從庫存繼承。它是否正確? 我試圖讓add()方法變成虛擬的,因爲它必須適用於劍,盾牌和魔藥對象。

+0

爲什麼這些項目從'庫存'繼承? – user463035818

+0

這個設計似乎有一個基本問題。將商品添加到庫存的唯一機制是'add()',它只能將商品添加到自身。作爲庫存的劍的概念很奇怪。 –

+0

你的班級結構令人困惑 - 「Sword' *是一個*庫存」是怎麼回事?爲什麼'Inventory'包含其他'Inventory'? – Quentin

回答

0

看來,你不小心將相同的名稱分配給兩個非常不同的類。

一個類是「Item」 - 而「Sword」擴展它。

class Sword: public Item {...}; 

另一類是「庫存」 - 它代表項目列表。

class Inventory 
{ 
    void add(Item*) {...} 
    ... 
    Item** inventory; 
}; 

然後你應該確保你只有一個庫存,而不是每個物品一個庫存。向這個庫存添加東西應該很容易。

Inventory* inventory = new Inventory(); 
Sword* sword = new Sword(1); 
inventory->add(sword); 

注意:應避免使用newdelete。儘可能使用標準容器(std::vector)。另外,儘可能使用智能指針(std::unique_ptr)。代替指針指針,使用智能指針列表:

Item** inventory; // works, but not so good 
std::vector<std::unique_ptr<Item>>; // better 

這是一個編碼實踐建議。它不會影響代碼的真實含義,它只是爲了減少混淆(例如,放置delete的位置,相當於new)。

+0

感謝您的明確回答! –

1

使用動態多態,我們可以創建一個抽象類Item,它描述了一個項目在清單中的功能。這很有用,因爲通過這樣的課程,可以管理我們不知道的項目,我們只知道它們會表現得像一個。

class Item 
{ 
public: 
    virtual ~Item() = default; 
    virtual const char* description() const = 0; 
}; 

進一步說,所有其他項目(劍,瓶子等),可以從該類繼承,從而使他們的是一個項目的特點:

class Sword: public Item 
{ 
public: 
    Sword() = default; 
    virtual ~Sword() = default; 
    const char* description() const override 
    { return "Sword"; } 
}; 

description方法,它覆蓋了Item::description抽象一個,所以只要你從Sword的實例調用.description,就會返回"Sword"字符串。例如:

Sword sword{}; 
Item& item = sword; 
std::puts(item.description()); // prints the "Sword" string. 

它現在更簡單的存儲項目,我們只需要使用它們的載體:std::vector<std::unique_ptr<Item>>

#include <vector> 
#include <memory> 

std::vector<std::unique_ptr<Item>> inventory{}; 
inventory.emplace_back(std::make_unique<Sword>()); 

但爲什麼我們不能有一個std::vector<Item>?僅僅因爲不可能從Sword構建Item。實際上,甚至不可能構建一個Item,因爲它具有抽象方法(即它們只是用來描述方法的原型,而不是它的定義/實現)。

std::unique_ptr是少數C++智能指針之一,它在那裏,所以我們不必手動處理分配。在代碼中使用newdelete可能會導致內存泄漏和災難,因爲程序員的注意力分散,所以智能指針會導致此問題不存在。

最後,爲了有一個項目回來,你可以簡單地向下轉換的東西回劍:

const auto& item = inventory[0]; // item is `const std::unique_ptr<Item>&` 
puts(item->description()); // prints "Sword" 
puts(dynamic_cast<Sword*>(item.get())->description()); // also prints "Sword" 

後者(使用的dynamic_cast)將創建一個轉變指針指向第一個項目,從item.get()method,但是以Sword*的形式。如果Sword中存在Item不常見的方法或數據成員,則需要執行此操作。例如,如果你有這樣的事情「詮釋sword_power`,你可以這樣做:

auto sword = dynamic_cast<Sword*>(item.get()); 
if (sword != nullptr) 
{ 
    std::printf("sword power: %d\n", sword->sword_power); 
} 

當然,如果檢查中投成功,是可選的,但這樣做可以防止你的代碼執行未定義行爲(以情況下,轉換失敗並返回一個空指針)。

目前仍然在做這個系統(之前,C++ 17),使用新的庫工具std::variant的另一種方式。

基本上,變種可以讓你一次有許多不同的類型之一帽子可以讓你有很多不同的類型(比如一個結構體),一個變體只允許一次從一個類型獲取一個值。爲了更好地理解它,這裏是它的工作原理是:

#include <variant> // C++17 

struct Sword {}; 
struct Bottle {}; 

std::variant<Sword, Bottle> item = Sword{}; 

std::tuple,一個變異體將具有其可能的類型時,模板參數作爲參數(即SwordBottle類型item的整體型的一部分) 。這樣,你可以一次有一把劍或一瓶,但永遠不會在同一時間。讓我們用這個新功能來實現我們的庫存。首先,我們必須改變我們的班了一下:

class Sword 
{ 
public: 
    int power; 

    Sword() = default; 
    const char* description() const 
    { return "Sword"; } 
}; 

class Bottle 
{ 
public: 
    bool empty; 

    Bottle() = default; 
    const char* description() const 
    { return "Bottle"; } 
}; 

我們刪除的虛擬方法和動態多態的需求,你會看得更遠,我們將不再需要動態分配了,因爲std::variant需要工作在堆棧中(這意味着程序也會更快(也許))。現在

,爲Item概念,我們做的變體的一個別名與我們的類:

using Item = std::variant<Sword, Bottle>; 

而且我們可以用向量也使用此:

std::vector<Item> inventory{}; 
inventory.emplace_back(Sword{}); 
inventory.emplace_back(Bottle{}); 

有幾種方法與這些項目互動,以防你需要他們回來。一種是使用std::holds_alternative

auto& item = inventory[0]; 

if (std::holds_alternative<Sword>(item)) 
{ 
    auto& sword = std::get<Sword>(item); 
    sword.power = 42; 
    std::printf("%s: %d\n", sword.description(), sword.power); 
} 

它檢查一個變體的對象是否被保持給予類型的值。在這種情況下,我們檢查了Sword。然後,如果那裏有一把劍,我們使用std::get<>獲得價值,它返回對我們物品的引用,作爲Sword

訪問實物的另一種方式是使用std::visit。簡而言之,訪問者就像一個具有重載功能的對象。你可以像一個函數一樣調用訪問者。爲了創建一個訪問者,我們可以使用一個結構超載的operator()或lambda表達式。這是第一個方法:

struct VisitItem 
{ 
    void operator() (Sword& sword) const 
    { 
     std::printf("%s: %d\n", sword.description(), sword.power); 
    } 

    void operator() (Bottle& bottle) const 
    { 
     std::printf("%s: %s\n", bottle.description(), 
        bottle.empty? "empty" : "full"); 
    } 
}; 

auto& item = inventory[0]; 
std::visit(VisitItem{}, item); // we give an instance of VisitItem for std::visit, and the item itself. 

這裏,std::visit將調用正確的operator()的變種內當前對象(即項目)。如果物品持有劍,則將調用operator() (Sword&)

另一種方法是製作超載的lambdas。這是一個有點複雜的是,因爲我們沒有爲圖書館的工具,但與C++ 17它實際上是更容易實現它:

template <typename... Ts> 
struct overload : Ts... 
{ 
    using Ts::operator()...; 
    template <typename... TTs> 
    constexpr explicit overload(TTs&&... tts) noexcept 
     : Ts{std::forward<TTs>(tts)}... 
    { 
    } 
}; 

template <typename... Ts> 
explicit overload(Ts&&...) -> overload<std::decay_t<Ts>...>; 

,然後用它像這樣:

auto& item = inventory[0]; 
auto visitor = overload(
    [] (Sword& s) { std::printf("power: %d\n", s.power); }, 
    [] (Bottle& b) { std::printf("%s\n", b.empty? "empty" : "full"); } 
); 

std::visit(visitor, item); 

如果您想了解overload結構中發生了什麼,它將繼承您所給出的所有lambda表達式,並將operator()重載帶入重載查找中(因爲來自基類的函數重載不被視爲候選對象,所以您必須using overload)。 overload結構之後的行是user-defined deduction guide,這意味着您可以根據構造函數更改模板結構的模板參數。