从向量中的父基转换的模板化子T生成的虚假字符 [英] Spurious Characters generated from templated Child T cast from Parent base in vector

查看:66
本文介绍了从向量中的父基转换的模板化子T生成的虚假字符的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm using a pattern of assigning a base class to a templated class so that I can put different types in a vector, vis-a-vis Attribute<String> and Attribute<int>, and the reason for this is that I want a vector containing different objects that inherit the same base object.

我收到的有关生成伪造文本的问题与一旦从向量中检索到基础对象并将其投射回原始的Attribute模板对象后生成的输出有关。

The problem that I am getting of spurious text being generated relates to the output that is generated once the Base object is retrieved from the vector and cast back to the original Attribute template object.

问题输出,使用内联注释显示输出与预期的差异:

Problem Output, using inline comments to show where output differed from expectation:


        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

这是可复制的示例,我添加了一个Makefile,该文件使用c ++编译器而不是Mac上的g ++编译器(在此操作中)C ++ 17尚未完全实现。

Here is the reproducible example, I've added a Makefile which uses the c++ compiler instead of g++ compiler as on Mac (where I am doing this) C++17 isn't fully implemented yet.

harness.cpp

harness.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,因为c ++编译器如果您不使用.cpp文件,则会生成令人讨厌的警告):

Attribute.cpp (here just for the sake of the Makefile because the c++ complier generates a nasty warning if you don't use a .cpp file):

#include "Attribute.h"

Attribute.h:

Attribute.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;
};

Makefile:

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

亲切的问候。

推荐答案

如评论中所述,此代码中的最大问题是对象切片并解决您应该使用基类指针或引用。在 vector 中,您可以存储指针,但不能存储真正的引用(不过,您可以使用 std :: reference_wrapper )。

As mentioned in the comments, the biggest problem in this code is object slicing and to work around that you should use base class pointers or references. In a vector you can store pointers but not real references (you can use std::reference_wrapper though).

您必须确定向量是应该拥有对象还是仅保留指向其寿命与向量分开控制的对象的指针。

You have to decide if the vector should own the objects or if it should only keep pointers to objects whos lifespan is controlled separately from the 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

在测试代码中,您使用了第一个选项,这样我就可以了,但是更改它很容易(并且通常是更可取的。)

In your test code, you've used the first option so I'll go with that, but it's easy (and often preferable) to change that.

这里是有关如何进行所需更改的想法。我希望代码中的注释能解释其中的大部分内容。

Here's an idea of how to make the changes needed. I hope the comments in the code explains most of it.

Attribute.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

harness.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";
        }
    }
}

我删除了对<生成文件中的code> Attributes.cpp ,因此您可以删除该文件。我还添加了一些在追查错误时可能会派上用场的事情,并为将< file> .cpp 映射到 obj /<文件> .o 。我使用了 gmake ,因此其中可能包含 gmake 特定的东西,使它在您这边失败了。在这种情况下,只需忽略它即可。某些选项可能仍然有用。

I removed the dependency to Attributes.cpp in the makefile so you can remove that file. I also added some things that can come in handy when chasing bugs and made a generic rule for mapping <file>.cpp to obj/<file>.o. I use gmake so it may contain gmake specific things that makes it fail on your side. Just disregard it in that case. Some of the options may still be useful.

Makefile

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)

这篇关于从向量中的父基转换的模板化子T生成的虚假字符的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆