c++ - 从 vector 中的父基转换的模板化子 T 生成的虚假字符

标签 c++ templates vector casting

<分区>

我正在使用一种将基类分配给模板化类的模式,这样我就可以将不同的类型放入一个 vector 中,相对于 Attribute<String>Attribute<int> ,这样做的原因是我想要一个包含继承相同基础对象的不同对象的 vector 。

我遇到的生成虚假文本的问题与从 vector 中检索 Base 对象并转换回原始 Attribute 模板对象后生成的输出有关。

问题输出,使用内联注释显示输出与预期不同的地方:


        T (String)
        ID: Id-1
        Key: -�'��,�'�8���Id-1  // expected key1
        Value:                  // expected one

        T (String)
        ID: Id-2
        Key: -�'��,�'�8���Id-2  // expected key2
        Value:                  // expected two

        T (String)
        ID: Id-3
        Key: -�'��,�'�8���Id-3  // expected key3
        Value:                  // expected three

        T (int)
        ID: Id-4
        Key: -�'��,�'�8���Id-4  // expected key4
        Value: 0                // expected 4

        T (String)
        ID: Id-5
        Key: -�'�-�'�8���Id-5   // expected key5
        Value:                  // expected 5

        T (int)
        ID: Id-6
        Key: -�'�0-�'�8���Id-6  // expected key6
        Value: 0                // expected 6

这是可重现的示例,我添加了一个使用 c++ 编译器而不是 g++ 编译器的 Makefile,因为在 Mac 上(我正在这样做)C++17 尚未完全实现。

线束.cpp

#include <iostream>
#include "Attribute.h"
#include <vector>

using namespace std;
using String = std::string;

int main()
{

    // TEST THE Attribute CLASS BY ITSELF

    Attribute <String> att("testkey","testvalue", TypeRef::String, "testId");

    cout << "Key: "+att.getKey() << endl;;
    cout << "Value: "+att.getValue() << endl;
    cout << "Id: "+att.getId() << endl;
    cout << endl;

    /* Output:

        Key: testkey
        Value: testvalue
        Id: testId

    */

    // TEST SIX INSTANCES OF Attribute CLASS BEFORE ADDING TO vector

    std::vector<AttributeObject> vector; 

    Attribute<String> q("key1","one",TypeRef::String, "Id-1"); AttributeObject &qBase = q;
    cout << "T (String)" << endl;
    cout << "Id1: " << q.getId() << endl;    
    cout << "Key1: " << q.getKey() << endl;
    cout << "Value1: " << q.getValue() << endl;

    cout << endl;

    Attribute<String> w("key2","two",TypeRef::String, "Id-2"); AttributeObject &wBase = w; 
    cout << "T (String)" << endl;
    cout << "Id2: " << w.getId() << endl;    
    cout << "Key2: " << w.getKey() << endl;
    cout << "Value2: " << w.getValue() << endl;

    cout << endl;

    Attribute<String> e("key3","three",TypeRef::String, "Id-3"); AttributeObject &eBase = e;
    cout << "T (String)" << endl;
    cout << "Id3: " << e.getId() << endl;    
    cout << "Key3: " << e.getKey() << endl;
    cout << "Value3: " << e.getValue() << endl;

    cout << endl;

    Attribute<int> r("key4",4,TypeRef::Int, "Id-4"); AttributeObject &rBase = r; 
    cout << "T (int)" << endl;
    cout << "Id4: " << r.getId() << endl;    
    cout << "Key4: " << r.getKey() << endl;
    cout << "Value4: " << r.getValue() << endl;

    cout << endl;

    Attribute<int> t("key5",5,TypeRef::String, "Id-5"); AttributeObject &tBase = t; 
    cout << "T (int)" << endl;
    cout << "Id5: " << t.getId() << endl;    
    cout << "Key5: " << t.getKey() << endl;
    cout << "Value5: " << t.getValue() << endl;

    cout << endl;

    Attribute<int> y("key6",6,TypeRef::Int, "Id-6"); AttributeObject &yBase = y; 
    cout << "T (int)" << endl;
    cout << "Id6: " << y.getId() << endl;    
    cout << "Key6: " << y.getKey() << endl;
    cout << "Value6: " << y.getValue() << endl;

    cout << endl;

    cout << endl;

    /* Output:

        T (String)
        Id1: Id-1
        Key1: key1
        Value1: one

        T (String)
        Id2: Id-2
        Key2: key2
        Value2: two

        T (String)
        Id3: Id-3
        Key3: key3
        Value3: three

        T (int)
        Id4: Id-4
        Key4: key4
        Value4: 4

        T (int)
        Id5: Id-5
        Key5: key5
        Value5: 5

        T (int)
        Id6: Id-6
        Key6: key6
        Value6: 6

    */

    vector.push_back(qBase);
    vector.push_back(wBase);
    vector.push_back(eBase);
    vector.push_back(rBase);
    vector.push_back(tBase);
    vector.push_back(yBase);

    // TEST ALL Attribute CLASS INSTANCES AS EXTRACTED FROM A vector

    int x = 0;
    for (AttributeObject baseObject : vector) {

        TypeRef typeRef = baseObject.getTypeRef();
        if(typeRef == TypeRef::String)
        {
            cout << endl;
            cout << "T (String)" << endl;
            Attribute <String> *pChild =  (Attribute <String> *) &baseObject;
            cout << "ID: " << pChild->getId() << endl; 
            const String sKey = pChild->getKey();
            cout << "Key: " << sKey << endl;
            const String sValue = pChild->getValue();
            cout << "Value: " << sValue << endl;
        }
        else if(typeRef == TypeRef::Int)
        {
            cout << endl;
            cout << "T (int)" << endl;
            Attribute <int> *pChild =  (Attribute <int> *) &baseObject;
            cout << "ID: " << pChild->getId() << endl; 
            const String sKey = pChild->getKey();
            cout << "Key: " << sKey << endl;
            const int iValue = pChild->getValue();
            cout << "Value: " << (int)iValue << endl;

        }
        x++;
    }

    /* Output (with differing expected values added as inline comments)

        T (String)
        ID: Id-1
        Key: -�'��,�'�8���Id-1  // expected key1
        Value:                  // expected one

        T (String)
        ID: Id-2
        Key: -�'��,�'�8���Id-2  // expected key2
        Value:                  // expected two

        T (String)
        ID: Id-3
        Key: -�'��,�'�8���Id-3  // expected key3
        Value:                  // expected three

        T (int)
        ID: Id-4
        Key: -�'��,�'�8���Id-4  // expected key4
        Value: 0                // expected 4

        T (String)
        ID: Id-5
        Key: -�'�-�'�8���Id-5   // expected key5
        Value:                  // expected 5

        T (int)
        ID: Id-6
        Key: -�'�0-�'�8���Id-6  // expected key6
        Value: 0                // expected 6
    */

    return 0;
}

Attribute.cpp(这里只是为了 Makefile,因为如果您不使用 .cpp 文件,c++ 编译器会生成一个讨厌的警告):

#include "Attribute.h"

属性.h:

#include <iostream>
#include <string>
#include <type_traits>
#include <vector>

using String = std::string;

enum class TypeRef {   
    String,
    Int
};

class AttributeObject{
    public:
            AttributeObject() {}
            AttributeObject(TypeRef typeRef, String Id) : typeRef(typeRef), id(Id) {}

            TypeRef getTypeRef()
            {
                return this->typeRef;
            }

            String getId()
            {
                return this->id;
            }


    protected:
            TypeRef typeRef;
            String id;
};

template<class T>
class Attribute : public AttributeObject {
public:
    using value_type = T;

    Attribute(const String& Key, const T& Value, const TypeRef& TypeRef, const String& Id) : 
        AttributeObject(TypeRef, Id),
        key(Key),        
        value(Value)
        {}

    String const& getKey() const {
        return key;
    };
    T const& getValue() const {
        return value;
    }

    TypeRef const& getTypeRef() const {
        return typeRef;
    }

private:
    String key;
    T value;
};

生成文件:

CC=c++
FLAGS=-c -g -std=c++17

All: build

mkdirs:
    # In mkdirs:
    mkdir -p obj

build: clean mkdirs harness.o Attribute.o
    # In build:
    $(CC) obj/harness.o obj/Attribute.o -o harness
    ls

harness.o: harness.cpp
    # harness.o:
    $(CC) $(FLAGS) harness.cpp -o obj/harness.o
    ls obj

Attribute.o: Attribute.cpp
    $(CC) $(FLAGS) Attribute.cpp -o obj/Attribute.o
    ls obj

clean:
    # In clean:
    rm -rf obj
    ls

亲切的问候。

最佳答案

如评论中所述,此代码中最大的问题是object slicing并解决这个问题,您应该使用基类指针或引用。在 vector你可以存储指针但不能存储真正的引用(尽管你可以使用 std::reference_wrapper)。

您必须决定 vector 是否应该拥有这些对象,或者它是否应该只保留指向对象的指针,这些对象的生命周期与 vector 分开控制。

std::vector<BaseClass*> v1;           // objects will live on even when the vector is destroyed
std::vector<std::unique_ptr<BaseClass>> v2; // objects are destroyed if the vector is destroyed

在您的测试代码中,您使用了第一个选项,所以我将采用第一个选项,但更改它很容易(而且通常更可取)。

以下是如何进行所需更改的想法。我希望代码中的注释解释了大部分内容。

属性.h

// add a header guard to not accidentally include it into the same translation unit more than once
#ifndef ATTRIBUTE_H
#define ATTRIBUTE_H

#include <iostream>
#include <string>
#include <typeinfo> // typeid()

using String = std::string;

// An abstract base class for all Attribute<T>'s
// Since "key" is common for them all, I've put it in here.
class AttributeBase {
public:
    AttributeBase(const String& k) : key(k) {}
    virtual ~AttributeBase() = 0; // pure virtual

    String const& getKey() const {
        return key;
    };

    // all descendants must implement a print method
    virtual std::ostream& print(std::ostream&) const = 0;

    // trust all Attribute<T>'s to get direct access to private members
    template<typename T>
    friend class Attribute;
private:
    String key;
};

// AttributeBase is an abstract base class but with a default
// destructor to not force descendants to have to implement it.
AttributeBase::~AttributeBase() {}

// streaming out any AttributeBase descendant will, via this method, call the virtual
// print() method that descendants must override 
std::ostream& operator<<(std::ostream& os, const AttributeBase& ab) {
    return ab.print(os);
}

template<class T>
class Attribute : public AttributeBase {
public:
    using value_type = T;

    Attribute(const String& Key, const T& Value) :
        AttributeBase(Key),
        value(Value)
    {}

    T const& getValue() const {
        return value;
    }

    std::ostream& print(std::ostream& os) const override {
        // Print an implementation defined name for the type using typeid()
        // and then "key" and "value".
        // Direct access to "key" works because of the "friend"
        // declaration in AttributeBase. We could have used getKey()
        // though, but this shows one use of "friend".
        return
            os << "type:  " << typeid(value).name() << "\n"
               << "key:   " << key << "\n"
               << "value: " << value << "\n";
    }

private:
    T value;
};

// end of header guard
#endif

线束.cpp

// include your own headers first to catch include chain errors more easily
#include "Attribute.h"

#include <iostream>
#include <vector>
#include <memory>

// using namespace std; // bad practice:
// https://stackoverflow.com/questions/1452721/why-is-using-namespace-std-considered-bad-practice

using String = std::string;

int main()
{
    // TEST THE Attribute CLASS BY ITSELF
    // in the following functions we're using the added operator<< to let the objects
    // print their own values

    Attribute <String> att("testkey","testvalue");
    std::cout << "-- att --\n" << att << "\n";

    // TEST SIX INSTANCES OF Attribute CLASS BEFORE ADDING TO attvec

    // use base class pointers to avoid slicing
    std::vector<AttributeBase*> attvec;

    Attribute<String> q("key1","one");
    std::cout << "-- q ---\n" << q << "\n";

    Attribute<String> w("key2","two");
    std::cout << "-- w ---\n" << w << "\n";

    Attribute<String> e("key3","three");
    std::cout << "-- e --\n" << e << "\n";

    Attribute<int> r("key4",4);
    std::cout << "-- r --\n" << r << "\n";

    Attribute<int> t("key5",5);
    std::cout << "-- t --\n" << t << "\n";

    Attribute<int> y("key6",6);
    std::cout << "-- y --\n" << y << "\n";

    // added a 7:th object with a different type

    Attribute<double> u("key7", 7.12345);
    std::cout << "-- u --\n" << u << "\n";

    // put pointers to the objects in the vector
    attvec.push_back(&q);
    attvec.push_back(&w);
    attvec.push_back(&e);
    attvec.push_back(&r);
    attvec.push_back(&t);
    attvec.push_back(&y);
    attvec.push_back(&u);

    // TEST ALL Attribute CLASS INSTANCES AS EXTRACTED FROM A vector
    std::cout << "--\n";

    for (AttributeBase const* baseObject : attvec) {

        // try to dynamic_cast to the types for which you have special handling
        // if( <init> ; <condition> ) { ...

        if(auto pChild = dynamic_cast<Attribute<String> const*>(baseObject); pChild)
        {
            std::cout << "T (String)\n";
            const String sKey = pChild->getKey();
            std::cout << "Key: " << sKey << "\n";
            const String sValue = pChild->getValue();
            std::cout << "Value: " << sValue << "\n";
            // or let the user defined streaming operator for the type do the work:
            std::cout << *pChild << "\n\n";
        }
        else if(auto pChild = dynamic_cast<Attribute<int> const*>(baseObject); pChild)
        {
            std::cout << "T (int)\n";
            const String sKey = pChild->getKey();
            std::cout << "Key: " << sKey << "\n";
            const int iValue = pChild->getValue();
            std::cout << "Value: " << iValue << "\n";
            // or let the user defined streaming operator for the type do the work:
            std::cout << *pChild << "\n\n";
        } else {
            std::cout << "T (generic)\n";
            const String sKey = baseObject->getKey();
            std::cout << "Key: " << sKey << "\n";
            /* the getValue() method does not exist in the base class
            auto genValue = baseObject->getValue();
            cout << "Value: " << genValue << "\n";
            */
            // or let the user defined streaming operator for the type do the work:
            std::cout << *baseObject << "\n";
        }
    }
}

我删除了对 Attributes.cpp 的依赖在 makefile 中,以便您可以删除该文件。我还添加了一些在追查错误时可以派上用场的东西,并制定了映射的通用规则 <file>.cppobj/<file>.o .我用 gmake所以它可能包含 gmake使它在你这边失败的具体事情。在这种情况下请忽略它。有些选项可能仍然有用。

生成文件

CC=c++

MINIMAL_WARNINGS=-Wall -Wextra -pedantic

BONUS_WARNINGS=-Werror -Wshadow -Weffc++ -Wconversion -Wsign-conversion -Woverloaded-virtual \
               -Wold-style-cast -Wwrite-strings -Wcast-qual -Wnoexcept -Wnoexcept-type \
               -Wpessimizing-move -Wredundant-move -Wstrict-null-sentinel -Wunreachable-code \
               -Wnull-dereference -Wsequence-point -pedantic-errors

# scan-build — Clang static analyzer
STATIC_ANALYSIS = scan-build -v --force-analyze-debug-code

# SANITIZER options using libasan.
# libasan - good for catching and displaying misc errors in runtime instead of just resulting
#           in a "Segmentation fault (core dumped)".
SANITIZER=-fsanitize=undefined -fsanitize=address

# turn on the bonus warnings if you'd like to fix misc things that are usually good to fix.
#WARNINGS=$(MINIMAL_WARNINGS) $(BONUS_WARNINGS)
WARNINGS=$(MINIMAL_WARNINGS)

FLAGS=-g3 -std=c++17 $(WARNINGS)

# collect all your .cpp files - remember to remove Attribute.cpp
SRC=$(wildcard *.cpp)

# Create a list of object files needed before linking.
# For each "%.cpp" file in SRC, "obj/%.o" will be put in OBJS.
OBJS=$(patsubst %.cpp,obj/%.o,$(SRC))

TARGETS=harness

All: $(TARGETS)

harness: $(OBJS)
        @# turn on SANITIZER on if you have libasan installed (linking will fail if you dont)
        @#$(CC) $(FLAGS) $(SANITIZER) -o harness $(OBJS)
        $(CC) $(FLAGS) -o harness $(OBJS)

# A generic object file rule. It requires a .cpp file and that the obj directory exists.
obj/%.o : %.cpp obj Attribute.h
        @# turn on STATIC_ANALYSIS if you have scan-build installed
        @#$(STATIC_ANALYSIS) $(CC) $(FLAGS) -c -o $@ $<
        $(CC) $(FLAGS) -c -o $@ $<

# The object directory target
obj:
        mkdir -p obj

clean:
        rm -rf obj $(TARGETS)

关于c++ - 从 vector 中的父基转换的模板化子 T 生成的虚假字符,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58235380/

相关文章:

c - 为什么不添加对纯 C 的模板支持?

android - 我可以在大型项目中使用 SVG 代替 PNG 吗

c++ - 可变参数宏中的模板推导失败

c++ - 如何在 opencv c++ 中为不同的 Matrix 类型编写方法而不明确指定 Mat 类型

Jquery tmpl 和谷歌搜索引擎优化

c++ - 快速 vector 初始化 C++

c++ - 在 C++ 中从字符串 vector 中删除一个字符

java - Java 中是否有等同于 C++ 成员函数指针的东西?

c++ - 用于创建业余操作系统的最受尊敬的语言和免费编译器?

c++ - 如何在自定义 std::ostream 类中使用 std::endl