C ++에 임의의 개체 목록을 저장합니다
-
03-07-2019 - |
문제
Java에서는 객체 목록을 가질 수 있습니다. 여러 유형의 객체를 추가 한 다음 검색하고 유형을 확인한 다음 해당 유형에 대한 적절한 작업을 수행 할 수 있습니다.
예 : (사과 코드가 정확하지 않으면 메모리에서 나옵니다)
List<Object> list = new LinkedList<Object>();
list.add("Hello World!");
list.add(7);
list.add(true);
for (object o : list)
{
if (o instanceof int)
; // Do stuff if it's an int
else if (o instanceof String)
; // Do stuff if it's a string
else if (o instanceof boolean)
; // Do stuff if it's a boolean
}
C ++ 에서이 동작을 복제하는 가장 좋은 방법은 무엇입니까?
해결책
Boost.variant 및 방문자를 사용한 예 :
#include <string>
#include <list>
#include <boost/variant.hpp>
#include <boost/foreach.hpp>
using namespace std;
using namespace boost;
typedef variant<string, int, bool> object;
struct vis : public static_visitor<>
{
void operator() (string s) const { /* do string stuff */ }
void operator() (int i) const { /* do int stuff */ }
void operator() (bool b) const { /* do bool stuff */ }
};
int main()
{
list<object> List;
List.push_back("Hello World!");
List.push_back(7);
List.push_back(true);
BOOST_FOREACH (object& o, List) {
apply_visitor(vis(), o);
}
return 0;
}
이 기술을 사용하는 데있어 한 가지 좋은 점은 나중에 변형에 다른 유형을 추가하고 방문자를 수정하여 해당 유형을 포함시키는 것을 잊어 버리면 컴파일되지 않는다는 것입니다. 너 가지다 가능한 모든 사례를 지원합니다. 반면, 스위치 또는 계단식 IF 문을 사용하는 경우 어디에서나 변경하고 버그를 도입하는 것을 잊기 쉽습니다.
다른 팁
boost::variant
Dirkgently의 제안과 유사합니다 boost::any
, 그러나 방문자 패턴을 지원하므로 나중에 유형 별 코드를 추가하는 것이 더 쉽습니다. 또한 동적 할당을 사용하지 않고 스택에 값을 할당하여 약간 더 효율적인 코드를 만듭니다.
편집하다: Litb가 주석에서 지적한대로 사용합니다 variant
대신에 any
미리 지정된 유형 목록 중 하나에서만 값을 유지할 수 있음을 의미합니다. 이것은 종종 강점이지만, 대문자의 경우 약점 일 수 있습니다.
다음은 예입니다 (방문자 패턴을 사용하지 않음).
#include <vector>
#include <string>
#include <boost/variant.hpp>
using namespace std;
using namespace boost;
...
vector<variant<int, string, bool> > v;
for (int i = 0; i < v.size(); ++i) {
if (int* pi = get<int>(v[i])) {
// Do stuff with *pi
} else if (string* si = get<string>(v[i])) {
// Do stuff with *si
} else if (bool* bi = get<bool>(v[i])) {
// Do stuff with *bi
}
}
(그렇습니다. 기술적으로 사용해야합니다 vector<T>::size_type
대신에 int
~을 위한 i
유형의 유형은 기술적으로 사용해야합니다 vector<T>::iterator
대신 어쨌든, 나는 그것을 간단하게 유지하려고 노력하고있다.)
C ++는 이종 용기를 지원하지 않습니다.
사용하지 않을 경우 boost
해킹은 더미 수업을 만들고이 더미 클래스에서 모든 다른 클래스를 파생시키는 것입니다. 더미 클래스 객체를 고정하기 위해 선택한 컨테이너를 만들면 갈 준비가되었습니다.
class Dummy {
virtual void whoami() = 0;
};
class Lizard : public Dummy {
virtual void whoami() { std::cout << "I'm a lizard!\n"; }
};
class Transporter : public Dummy {
virtual void whoami() { std::cout << "I'm Jason Statham!\n"; }
};
int main() {
std::list<Dummy*> hateList;
hateList.insert(new Transporter());
hateList.insert(new Lizard());
std::for_each(hateList.begin(), hateList.end(),
std::mem_fun(&Dummy::whoami));
// yes, I'm leaking memory, but that's besides the point
}
당신이 사용할 경우 boost
당신은 시도 할 수 있습니다 boost::any
. 여기 사용의 예입니다 boost::any
.
당신은 이것을 훌륭하다고 생각할 수 있습니다 기사 두 명의 주요 C ++ 관심 전문가에 의해.
지금, boost::variant
AS를 찾아야 할 또 다른 것입니다 j_random_hacker 말하는. 그래서 여기에 a 비교 무엇을 사용할 것인지에 대한 공정한 아이디어를 얻기 위해.
a boost::variant
위의 코드는 다음과 같습니다.
class Lizard {
void whoami() { std::cout << "I'm a lizard!\n"; }
};
class Transporter {
void whoami() { std::cout << "I'm Jason Statham!\n"; }
};
int main() {
std::vector< boost::variant<Lizard, Transporter> > hateList;
hateList.push_back(Lizard());
hateList.push_back(Transporter());
std::for_each(hateList.begin(), hateList.end(), std::mem_fun(&Dummy::whoami));
}
그런 종류의 일이 실제로 얼마나 자주 유용합니까? 나는 C ++로 몇 년 동안, 다른 프로젝트에서 프로그래밍 해 왔으며 실제로 이종 컨테이너를 원하지 않았습니다. 어떤 이유로 든 Java에서 일반적 일 수 있지만 (Java 경험이 훨씬 적습니다) Java 프로젝트에서는 IT를 사용하기 위해서는 C ++에서 더 잘 작동하는 다른 일을 할 수있는 방법이있을 수 있습니다.
C ++는 Java보다 유형 안전성에 중점을두고 있으며 이는 매우 유형이 없습니다.
즉, 물건에 공통점이 없다면 왜 함께 저장하고 있습니까?
그들이 공통점이 있다면, 당신은 그들이 상속 될 수 있도록 수업을 만들 수 있습니다. 또는 Boost :: Any. 그들이 상속하는 경우, 전화 할 가상 기능이 있거나 실제로 필요한 경우 Dynamic_cast <>을 사용하십시오.
나는 유형을 기준으로 분기하기 위해 동적 유형 주조를 사용하여 종종 아키텍처의 결함을 암시한다는 것을 지적하고 싶습니다. 대부분의 경우 가상 기능을 사용하여 동일한 효과를 얻을 수 있습니다.
class MyData
{
public:
// base classes of polymorphic types should have a virtual destructor
virtual ~MyData() {}
// hand off to protected implementation in derived classes
void DoSomething() { this->OnDoSomething(); }
protected:
// abstract, force implementation in derived classes
virtual void OnDoSomething() = 0;
};
class MyIntData : public MyData
{
protected:
// do something to int data
virtual void OnDoSomething() { ... }
private:
int data;
};
class MyComplexData : public MyData
{
protected:
// do something to Complex data
virtual void OnDoSomething() { ... }
private:
Complex data;
};
void main()
{
// alloc data objects
MyData* myData[ 2 ] =
{
new MyIntData()
, new MyComplexData()
};
// process data objects
for ( int i = 0; i < 2; ++i ) // for each data object
{
myData[ i ]->DoSomething(); // no type cast needed
}
// delete data objects
delete myData[0];
delete myData[1];
};
안타깝게도 C ++에서는 쉽게 수행 할 수있는 방법이 없습니다. 기본 클래스를 직접 만들고이 수업에서 다른 모든 클래스를 도출해야합니다. 기본 클래스 포인터의 벡터를 작성한 다음 Dynamic_cast (자체 런타임 오버 헤드와 함께 제공)를 사용하여 실제 유형을 찾으십시오.
이 주제의 완전성을 위해서는 Void*를 사용하여 Pure C로 실제로 수행 할 수 있다고 언급하고 싶습니다. 나에게 코드). 객체가 어떤 유형인지 알고 있거나 그에 대한 어딘가에 필드를 저장하는 경우에는 효과가 있습니다. 당신은 가장 확실히 이것을하고 싶지 않지만 여기에 가능하다는 것을 보여주는 예가 있습니다.
#include <iostream>
#include <vector>
using namespace std;
int main() {
int a = 4;
string str = "hello";
vector<void*> list;
list.push_back( (void*) &a );
list.push_back( (void*) &str );
cout << * (int*) list[0] << "\t" << * (string*) list[1] << endl;
return 0;
}
컨테이너에 원시 유형을 저장할 수는 없지만 Java의자가 옥스 원시 유형과 유사한 원시 유형 래퍼 클래스를 생성 할 수 있습니다 (예에서는 원시 유형 리터럴이 실제로 자서전됩니다). 그 인스턴스는 C ++ 코드로 나타나고 (거의) 원시 변수/데이터 부재와 마찬가지로 사용될 수 있습니다.
보다 내장 유형의 객체 포장지 ~에서 C ++의 객체 지향 설계 패턴을 갖는 데이터 구조 및 알고리즘.
래핑 된 객체를 사용하면 C ++ typeid () 연산자를 사용하여 유형을 비교할 수 있습니다. 다음 비교가 작동 할 것이라고 확신합니다.if (typeid(o) == typeid(Int))
int가 Int Primitive 유형 등의 래핑 클래스가되는 곳 ...if (o.get_typeid() == typeid(Int))
...
즉, 당신의 모범과 관련하여 이것은 나에게 코드 냄새가납니다. 이것이 당신이 객체의 유형을 확인하는 유일한 장소가 아니라면, 나는 다형성을 사용하는 경향이 있습니다 (특히 유형과 관련하여 특정 다른 방법/기능이있는 경우). 이 경우 나는 래핑 된 각 원시 클래스에서 구현 될 연기 방법 ( 'do do the the the the the the the the the the the the the affed a do'for 'do sittue')을 추가하는 원시 포장지를 사용합니다. 이를 통해 컨테이너 반복기를 사용하고 IF 문을 제거 할 수 있습니다 (다시 한 번 유형을 비교 한 경우이를 위해 다형성을 사용하여 연기 된 방법을 설정하는 것은 과잉입니다).
나는 상당히 경험이 없지만 여기에 내가 갈 일이 있습니다.
- 조작 해야하는 모든 클래스에 대한 기본 클래스를 만듭니다.
- 컨테이너 클래스/ 재사용 컨테이너 클래스를 작성하십시오. (다른 답변을 본 후 개정 -내 이전 요점은 너무 비밀 스럽습니다.)
- 비슷한 코드를 작성하십시오.
훨씬 더 나은 솔루션이 가능할 것이라고 확신합니다. 또한 더 나은 설명이 가능할 것이라고 확신합니다. 나는 나쁜 C ++ 프로그래밍 습관이 있다는 것을 배웠기 때문에 코드에 들어 가지 않고 아이디어를 전달하려고 노력했습니다.
이게 도움이 되길 바란다.
사실 외에도, 대부분이 지적했듯이, 당신은 그렇게 할 수 없거나, 더 중요한 것은, 아마도 당신은 정말로 원하지 않습니다.
당신의 모범을 무시하고 실제 예에 더 가까운 것을 고려해 봅시다. 특히, 실제 오픈 소스 프로젝트에서 본 일부 코드. 캐릭터 배열에서 CPU를 모방하려고 시도했습니다. 따라서 OP 코드를 기반으로 문자, 정수 또는 문자열에 대한 포인터가 될 수있는 1 바이트 "OP 코드"가 배열에 들어갑니다. 이를 처리하기 위해서는 많은 비트 홀딩과 관련이있었습니다.
내 간단한 해결책 : 4 개 별도의 스택 <> : 하나는 "opcode"열거와 각각 숯, int 및 문자열 용입니다. 다음으로 Opcode 스택에서 벗어나면 다른 세 가지 중 어느 쪽이 피연산자를 얻을 수 있습니다.
실제 문제를 비슷한 방식으로 처리 할 수있는 가능성이 매우 높습니다.
글쎄, 당신은 기본 클래스를 만들고 그것을 상속하는 클래스를 만들 수 있습니다. 그런 다음 std :: 벡터에 보관하십시오.
짧은 대답은 ... 당신은 할 수 없습니다.
긴 대답은 ... 모두 기본 객체에서 상속되는 자신의 새로운 객체를 정의해야합니다. Java에서 모든 개체는 궁극적으로 "물체"에서 내려갑니다.
C ++의 RTTI (실행 시간 유형 정보)는 항상 힘든, 특히 크로스 컴파일러였습니다.
가장 좋은 옵션은 STL을 사용하고 객체 유형을 결정하기 위해 인터페이스를 정의하는 것입니다.
public class IThing
{
virtual bool isA(const char* typeName);
}
void myFunc()
{
std::vector<IThing> things;
// ...
things.add(new FrogThing());
things.add(new LizardThing());
// ...
for (int i = 0; i < things.length(); i++)
{
IThing* pThing = things[i];
if (pThing->isA("lizard"))
{
// do this
}
// etc
}
}
마이크