2009-06-05 24 views
10

Trong nhiệm vụ sử thi C++ của tôi, hãy làm những việc không nên, tôi đang cố gắng tổng hợp một lớp thời gian biên dịch.Tạo mã C++

Dựa trên một định nghĩa preprocessor, chẳng hạn như (khái niệm thô)

CLASS_BEGIN(Name) 
    RECORD(xyz) 
    RECORD(abc) 

    RECORD_GROUP(GroupName) 
     RECORD_GROUP_RECORD(foo) 
     RECORD_GROUP_RECORD(bar) 
    END_RECORDGROUP 
END_CLASS 

Trong khi Tôi khá chắc chắn tôi tạo ra một lớp học mà đọc dữ liệu từ hệ thống tập tin sử dụng loại cấu trúc này (Thậm chí có thể làm việc đó bằng cách sử dụng Lập trình meta mẫu), tôi không thấy cách tôi có thể tạo cả hai hàm để truy cập dữ liệu và hàm để đọc dữ liệu.

tôi muốn kết thúc với một cái gì đó như thế này lớp

class Name{ 
    public: 
    xyz_type getxyz(); 
    void setxyz(xyz_type v); 

    //etc 

    list<group_type> getGroupName(); 

    //etc 

    void readData(filesystem){ 
     //read xyz 
     //read abc 
     //etc 
    } 
}; 

Có ai có bất kỳ ý tưởng nếu điều này thậm chí còn có thể?

--EDIT--

Để làm rõ mục đích sử dụng cho mục đích này. Tôi có tệp ở định dạng chuẩn mà tôi muốn đọc. Định dạng đã được định nghĩa rồi, do đó, nó không mở để thay đổi. Mỗi tệp có thể chứa bất kỳ bản ghi số nào, mỗi bản ghi có thể chứa bất kỳ bản ghi phụ số nào.

Nhiều loại bản ghi chứa từng loại bản ghi phụ khác nhau, nhưng chúng có thể được xác định. Vì vậy, ví dụ bản ghi Heightmap phải chứa một Heightmap, nhưng có thể tùy chọn chứa các normals.

Vì vậy, tôi muốn xác định một bản ghi cho rằng như vậy:

CLASS_BEGIN(Heightmap) 
    RECORD(VHDT, Heightmap, std::string) //Subrecord Name, Readable Name, Type 
    RECORD_OPTIONAL(VNML, Normals, std::string) 
END_CLASS 

Đối với mà tôi muốn ra cái gì đó với các chức năng của một lớp học như thế này:

class Heightmap{ 
    public: 
    std::string getHeightmap(){ 
     return mHeightmap->get<std::string>(); 
    } 
    void setHeightmap(std::string v){ 
     mHeight->set<std::string>(v); 
    } 

    bool hasNormal(){ 
     return mNormal != 0; 
    } 
    //getter and setter functions for normals go here 

    private: 
    void read(Record* r){ 
     mHeightmap = r->getFirst(VHDT); 
     mNormal = r->getFirst(VNML); 
    } 


    SubRecord* mHeightmap, mNormal; 
} 

Vấn đề này Tôi đang có là tôi cần mỗi định nghĩa tiền xử lý hai lần. Một lần để xác định định nghĩa hàm trong lớp và một lần để tạo hàm đọc. Vì bộ tiền xử lý hoàn toàn có chức năng, tôi không thể đẩy dữ liệu vào hàng đợi và tạo lớp trên định nghĩa marco END_CLASS.

Tôi không thể nhìn thấy một cách giải quyết vấn đề này, nhưng tự hỏi liệu có ai hiểu rõ hơn về C++ không.

+0

Có thể, tôi nghi ngờ. Bạn có thể chính xác hơn về nơi bạn đang va vào tường? –

+0

Nó không rõ ràng những lợi thế bạn đạt được từ việc này - xin vui lòng giải thích. –

Trả lời

5

Bạn có thể giải quyết vấn đề này bằng cách sử dụng tăng tuples. Nó sẽ dẫn đến một thiết kế khác với những gì bạn đang nghĩ đến bây giờ, nhưng nó sẽ cho phép bạn giải quyết vấn đề một cách chung chung.

Ví dụ sau định nghĩa bản ghi của biểu mẫu "std :: string, bool" và sau đó đọc dữ liệu đó từ một luồng.

#include "boost/tuple/tuple.hpp" 
#include <iostream> 
#include <sstream> 

using namespace ::boost::tuples; 

Các chức năng được sử dụng để đọc dữ liệu từ istream. Quá tải đầu tiên dừng lặp lại thông qua bộ dữ liệu sau khi chúng tôi đạt được loại bản ghi cuối cùng:

// 
// This is needed to stop when we have no more fields 
void read_tuple (std::istream & is, boost::tuples::null_type) 
{ 
} 

template <typename TupleType> 
void read_tuple (std::istream & is, TupleType & tuple) 
{ 
    is >> tuple.template get_head(); 
    read_tuple (is, tuple.template get_tail()); 
} 

Lớp sau thực hiện thành viên getter cho Hồ sơ của chúng tôi. Sử dụng RecordKind làm khóa của chúng tôi, chúng tôi có được thành viên cụ thể mà chúng tôi quan tâm.

template <typename TupleType> 
class Record 
{ 
private: 
    TupleType m_tuple; 

public: 
    // 
    // For a given member - get the value 
    template <unsigned int MBR> 
    typename element <MBR, TupleType>::type & getMember() 
    { 
    return m_tuple.template get<MBR>(); 
    } 

    friend std::istream & operator>> (std::istream & is 
            , Record<TupleType> & record) 
    { 
    read_tuple (is, record.m_tuple); 
    } 
}; 

Loại tiếp theo là mô tả meta cho hồ sơ của chúng tôi. Điều tra cho chúng ta một cái tên tượng trưng mà chúng ta có thể sử dụng để truy cập các thành viên, tức là. tên trường. Các tuple sau đó xác định loại của các lĩnh vực:

struct HeightMap 
{ 
    enum RecordKind 
    { 
    VHDT 
    , VNML 
    }; 

    typedef boost::tuple < std::string 
         , bool 
        > TupleType; 
}; 

Cuối cùng, chúng ta xây dựng một kỷ lục và đọc bằng một số dữ liệu từ một dòng:

int main() 
{ 
    Record<HeightMap::TupleType> heightMap; 
    std::istringstream iss ("Hello 1"); 

    iss >> heightMap; 

    std::string s = heightMap.getMember <HeightMap::VHDT>(); 
    std::cout << "Value of s: " << s << std::endl; 


    bool b = heightMap.getMember <HeightMap::VNML>(); 
    std::cout << "Value of b: " << b << std::endl; 
}  

Và vì đây là tất cả các mẫu mã, bạn nên có có thể có các bản ghi được lồng trong các bản ghi.

8

Nếu bạn đang tìm kiếm một cách để tuần tự hóa/deserialize dữ liệu với việc tạo mã C++, tôi sẽ xem các protobufs của Google (http://code.google.com/p/protobuf/) hoặc Tiết kiệm Facebook (http://incubator.apache.org/thrift/).

Đối protobufs, bạn viết một định nghĩa dữ liệu như vậy:

message Person { 
    required string name = 1; 
    required int32 id = 2; 
    optional string email = 3; 

    enum PhoneType { 
    MOBILE = 0; 
    HOME = 1; 
    WORK = 2; 
    } 

    message PhoneNumber { 
    required string number = 1; 
    optional PhoneType type = 2 [default = HOME]; 
    } 

    repeated PhoneNumber phone = 4; 
} 

Một Person lớp C++ sau đó được tạo ra cho phép bạn tải, lưu và truy cập vào dữ liệu này. Bạn cũng có thể tạo ra python, java vv

2

tôi có thể chơi xung quanh với một mixin kỷ lục để làm điều gì đó tương tự - thêm chức năng cho một lớp Automagically tại thời gian biên dịch

template<class Base, class XyzRecType> 
    class CRecord : public Base 
    { 
    protected: 
     RecType xyz; 
    public: 
     CRecord() : Base() {} 


     RecType Get() {return xyz;} 

     void Set(const RecType& anXyz) {xyz = anXyz;} 

     void ReadFromStream(std::istream& input) 
     { 
      ... 
     } 

    }; 

    class CMyClass 
    { 
    }; 

    int main() 
    { 
     // now thanks to the magic of inheritance, my class has added methods! 
     CRecord<CMyClass, std::string> myClassWithAStringRecord; 

     myClassWithAStringRecord.Set("Hello"); 

    } 
0

Tôi không chắc chắn chính xác những gì bạn đang tìm kiếm trong một số trường hợp.

  • Điều gì xảy ra với foo và thanh trong đặc điểm kỹ thuật?
  • getGroupName thực sự trở lại là gì? (foo, bar)? hoặc GroupName?

Dường như bạn đang cố gắng tạo cơ chế tải và truy cập cấu trúc trên đĩa của bố cục tùy ý. Nó thật sự đúng? (Chỉnh sửa: Chỉ cần nhận thấy chức năng thành viên "set" ... vì vậy tôi đoán bạn đang tìm kiếm serialization đầy đủ)

Nếu bạn đang sử dụng hệ thống * nix, hãy chỉ định trình biên dịch của riêng bạn để biên dịch .o một perl/python/những gì-có-bạn kịch bản kết thúc với một cuộc gọi đến gcc) trong Makefile là một giải pháp tầm thường. Những người khác có thể biết cách làm điều này trên cửa sổ.

3

Đây là kỹ thuật tôi sử dụng rất nhiều trong C và C++, được gọi là "danh sách macro". Giả sử bạn có một danh sách các thứ như biến, thông báo lỗi, opcodes phiên dịch hoặc bất kỳ thứ gì về mã lặp lại cần phải được viết. Trong trường hợp của bạn nó là biến thành viên lớp.

Giả sử đó là các biến. Đặt chúng trong một macro danh sách như thế này:

#define MYVARS \ 
DEFVAR(int, a, 6) \ 
DEFVAR(double, b, 37.3) \ 
DEFARR(char, cc, 512) \ 

Để khai báo các biến, làm điều này:

#define DEFVAR(typ,nam,inival) typ nam = inival; 
#define DEFARR(typ,nam,len) typ nam[len]; 
    MYVARS 
#undef DEFVAR 
#undef DEFARR 

Bây giờ bạn có thể tạo ra bất kỳ loại mã lặp đi lặp lại chỉ bằng cách xác định lại DEFVAR và DEFARR, và instantiating MYVARS .

Một số người tìm thấy điều này khá khó chịu, nhưng tôi nghĩ rằng đó là một cách hoàn toàn tốt để sử dụng bộ tiền xử lý như một bộ tạo mã và hoàn thành DRY. Và, chính macro danh sách sẽ trở thành một mini-DSL.

+0

Trong C, vâng. Thường thì trong C++ tôi sẽ tìm một giải pháp TMP trước tiên. Điều này có thể đi một số cách để giải thích tại sao tôi làm việc hiệu quả hơn trong C. –

+0

Sở thích của tôi giữa C và C++ phụ thuộc vào những gì tôi đang làm, nhưng tôi hoàn toàn hiểu. –

1

Nói chung, bạn có thể thực hiện chính xác những gì bạn muốn nếu bạn hợp nhất mọi thứ thành một macro và sau đó tận dụng thư viện Booost Preprocessor để xác định lớp của bạn.Hãy xem tôi đã triển khai macro MACE_REFLECT như thế nào để thực hiện một phần chuyên môn của toàn bộ lớp và phải tham chiếu mỗi tên hai lần trong các phần khác nhau.

Điều này rất giống với cách tôi tự động phân tích cú pháp JSON thành các cấu trúc với sự trợ giúp của bộ xử lý trước.

Với ví dụ của bạn, tôi sẽ dịch nó như vậy:

struct Name { 
    xyz_type xyz; 
    abc_type abc; 
    boost::optional<foo_type> foo; 
    boost::optional<bar_type> bar; 
}; 
MACE_REFLECT(Name, (xyz)(abc)(foo)(bar)) 

tôi bây giờ có thể 'chuyến viếng thăm' các thành viên của danh từ phân tích cú pháp của tôi:

struct visitor { 
    template<typename T, T p> 
    inline void operator()(const char* name)const { 
     std::cout << name << " = " << c.*p; 
    } 
    Name c; 
}; 
mace::reflect::reflector<Name>::visit(visitor()); 

Nếu đối tượng của bạn có thể được biểu diễn dưới dạng structs, array, key-value-pairs và primitives, sau đó kỹ thuật này hoạt động kỳ diệu và cho tôi serialization/deserializtion ngay lập tức đến/từ json/xml hoặc định dạng bản ghi tùy chỉnh của bạn.

https://github.com/bytemaster/mace/blob/master/libs/rpc/examples/jsonv.cpp

+0

upvoted cho ác mát mẻ của nó, nhưng, AFAICU (HYGWIMBTA = "Hy vọng bạn có được điều tôi muốn đó viết tắt"), ông không muốn viết tên trường hai lần. Concat của bạn. macro yêu cầu điều đó. –