Thread: Data structure(s) choice for specific use case

  1. #1
    Registered User
    Join Date
    Jun 2017
    Posts
    5

    Lightbulb Data structure(s) choice for specific use case

    Hello everyone

    I hope this is the right board to post my question.

    First let me provide you with some insight. I recently started to learn C++11 (last time I used C++ was 10 years about and it was basic stuff only) and Qt framework and decided to use my so far limited knowledge to build my first C++ more/less useful application (at least for my own use).

    Basically I will be reading data from data source (be it database or a file or network stream) and storing in a collection of objects of class Record.

    Record contains variety of integrals, few string and few booleans. It may also reference other classes in the future, but let's focus on current situation.

    So the idea is, I will need to pass that data to QtAbstractListModel implementation. The fact is you don't need to know much about the model, other than it must allow for:
    1. Accessing given collection by index (and properties of underlying object at that index)
    2. Modify the object at given index
    3. Insert new objects
    4. Delete existing objects by index
    5. Searching for object by properties

    In addition to that, my collection held by the model must be sorted by the property (unique identifier) at all times, meaning the insert should place the item based on its identifier (uint16_t if that matters) - what is important I think is that the identifiers are not continuous and will not start at 0 or 1.

    The model will be used by Qt based UI (QListView) which does require points 1 & 2. The reason for keeping order of elements is because the list displayed should be sorted by identifier.

    So the question is, what kind of data structure or combination of data structures would be good here? The frequency of operations I think will be as follows:
    1. Lots of read -> modify
    2. Few insertions
    3. Very few deletions

    Just to clarify it, I'm not looking for the code here, I'm able to write it myself, rather than that, I would like to verify my design considerations and perhaps a better solution you may know of. So here is what I though of:

    1. my Loader class will load data into (it will be initially sorted by identifier)
      Code:
      Vector<Record>
    2. I will then expose it from Loader via method below, passing the pointer later to my model.
      Code:
      Vector<Record> * getRecords();
    3. My model will then:
      1. Create std::set<uint16_t> where I would store all identifiers.
      2. Create std::map<uint16_t, size_t> where key will store the identifier and value will be index of Vector<Record> associated with this identifier.


    So the read / access would be as follows:
    Code:
    // this is pseudo code
    [return type] getRecord(int index) {
        uint16_t identifier = my_set[index];
        size_t vector_index = my_map[identifier]; 
        auto record = (*my_vector_ptr)[vector_index]; // vector is a pointer, just a reminder
        
        /* here I'm also not sure what should be returned really? As far as I understand record will be const_iterator, is returning it a bad thing? */
        return record; 
    }
    While insert could look like this:
    Code:
    boolean/void insertRecord(Record record) {
        my_vector_ptr->push_back(record);
        my_set.insert(record->getIdentifier());
        /* here I'm missing a way to get newly inserted element's index in my_set */
        my_map[record->getIdentifier()] = my_new_element_set_index // the one I must retrieve above
    }
    Would mean appending element to the original vector (via pointer I have in the model), inserting element's identifier into a set and then inserting the identifier and vector position of new element into a map. I did not yet figure out deletion, but this will be very rare case, so even if means deletion will be far from optimal, I am fine with it.

    Of course these data structures would be encapsulated within a class to make sure any insert / deletion will keep them synchronized.

    So, would you find this solution to be good or perhaps there's something, well better / simpler to deal with that scenario? Also, would you help me filling the gaps (if you find my solution correct) in my example codes (getting index of element after set.insert and the return type of my getRecord method)?
    Last edited by Zikk; 06-24-2017 at 06:17 AM. Reason: spelling

  2. #2
    Guest
    Guest
    I might be totally missing the bigger picture here, but why is the Vector<Records> kept around, and why the indirection via std::set? Couldn't you work with a std::map<uint16_t, Record> alone?

    From what I understand, the load action is rare and (like a File > Open type of action) you want good performance working with the loaded dataset.

    If you could give some estimate about the number of Records you'll store, that should help others make better suggestions. The right choice of data structure often depends on that.

  3. #3
    Registered User
    Join Date
    Jun 2017
    Posts
    5
    Quote Originally Posted by Guest View Post
    I might be totally missing the bigger picture here, but why is the Vector<Records> kept around, and why the indirection via std::set? Couldn't you work with a std::map<uint16_t, Record> alone?

    From what I understand, the load action is rare and (like a File > Open type of action) you want good performance working with the loaded dataset.

    If you could give some estimate about the number of Records you'll store, that should help others make better suggestions. The right choice of data structure often depends on that.
    Let me answer your questions one by one then:

    I might be totally missing the bigger picture here, but why is the Vector<Records> kept around
    How else can I do this without copying the data? I may be missing something too (since my background is mostly PHP and .NET), but Vector<Record> contains my Record instances. It resides in Loader class instance and I'd like to avoid copying it, while I need to access its data in my model. Any other way to deal with this?

    and why the indirection via std::set? Couldn't you work with a std::map<uint16_t, Record> alone?
    Can I access the map by index in performant manner then? Since all operations (apart from insert obviously) will access my data by index (not Record identifier). How do you see that being done with std::map? (and this is the actual question, because perhaps I missed something there and you know of a better solution without that kind of indirection)

    From what I understand, the load action is rare and (like a File > Open type of action) you want good performance working with the loaded dataset.
    Yes, indeed. For the beginning I will be using sqlite database (so file > open is exactly what is going to happen) and move to flatbuffer based binary file later.

    If you could give some estimate about the number of Records you'll store, that should help others make better suggestions. The right choice of data structure often depends on that.
    Hard to say, but I would say about 30000 to begin with with little growth, let's say up to 40000-45000 during the lifetime of this application. I can't tell you for sure how "big" will the Record class get though, but given my best approximation I think it will hold no more than two dozens of variables, half of them being booleans, 2-3 strings and the rest of them being integrals of various sizes.
    Last edited by Zikk; 06-24-2017 at 07:23 AM.

  4. #4
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,661
    There is such a thing as premature optimisation disease.

    The point would be to make sure you have the interfaces nailed down to do what you want.
    Pick the simplest approach which gets you something functional.

    Exotic approaches may be more efficient, but they take longer to write and longer to debug. You really need to know whether the simple approach is a huge performance bottleneck from actual performance data (not speculative guessing in advance) before embarking on optimisation.

    The point is, if your interface is stable and complete, you can fiddle around with the innards to your hearts content. After each attempt at optimisation, you'll be able to answer two questions
    - does it still work (use the test suite you created the first time around)?
    - is it usefully quicker / less memory given the complexity of the change?
    If the answer to either is 'no', then you can simply revert back to the known good version and try again.

    To answer your excessive copying worry, make use of const references. Nothing is copied, but the const makes sure nobody is going to making any unauthorised changes to your data.

    You haven't said
    - how much data (a few KB or a few GB - though with only 16-bit identifiers, how much could you realistically use, unless Record's are huge)
    - how often read / modify / delete happens (100's of times a second, or as fast as a user can comprehend and click)
    - how powerful the machine is (CPU/Memory/Disk/Network).
    so figuring out the 'best' data structure is hard.

    > Hard to say, but I would say about 30000 to begin with with little growth, let's say up to
    > 40000-45000 during the lifetime of this application.
    Having a 16-bit identifier seems like a Y2K-like problem next year for you, not in a decades time.

    "It is easier to make a working program optimal, than it is to make an optimal program work".
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  5. #5
    Registered User
    Join Date
    Jun 2017
    Posts
    5
    Quote Originally Posted by Salem View Post
    There is such a thing as premature optimisation disease.

    The point would be to make sure you have the interfaces nailed down to do what you want.
    Pick the simplest approach which gets you something functional.

    Exotic approaches may be more efficient, but they take longer to write and longer to debug. You really need to know whether the simple approach is a huge performance bottleneck from actual performance data (not speculative guessing in advance) before embarking on optimisation.

    The point is, if your interface is stable and complete, you can fiddle around with the innards to your hearts content. After each attempt at optimisation, you'll be able to answer two questions
    - does it still work (use the test suite you created the first time around)?
    - is it usefully quicker / less memory given the complexity of the change?
    If the answer to either is 'no', then you can simply revert back to the known good version and try again.

    To answer your excessive copying worry, make use of const references. Nothing is copied, but the const makes sure nobody is going to making any unauthorised changes to your data.

    You haven't said
    - how much data (a few KB or a few GB - though with only 16-bit identifiers, how much could you realistically use, unless Record's are huge)
    - how often read / modify / delete happens (100's of times a second, or as fast as a user can comprehend and click)
    - how powerful the machine is (CPU/Memory/Disk/Network).
    so figuring out the 'best' data structure is hard.

    > Hard to say, but I would say about 30000 to begin with with little growth, let's say up to
    > 40000-45000 during the lifetime of this application.
    Having a 16-bit identifier seems like a Y2K-like problem next year for you, not in a decades time.

    "It is easier to make a working program optimal, than it is to make an optimal program work".
    So yes, you're mostly right here and I'm aware of that. Normally I wouldn't do that and would go for the simplest solution, using just a vector (which would mean insert or removal of Record instance result in partial copy of my vector). I'm also pretty sure that vector is enough here, because any changes will be done using GUI, so as long as insert / removal does not take seconds to be performed the user will not notice the difference.

    The reason I want it to be optimized is stricly educational. Other than that I don't see a reason for this kind of optimization and you're right about that.

    As for const references, would you be so kind and give me a rough example of how would my (pseudo) code look like? So how I should declare my vector then and what do I return from my loader? Once I know this I will know how to adjust the rest (my model) to work with that.

    To answer your questions:
    - I think few MB, the Record will only hold few strings, plenty of booleans and several integers
    - It's going to be modified only via GUI, so I would say that modifying the record, while most frequent (apart from reading its properties), will still only be done few times a minute at most, inserts and deletes may never happen during a single run of application, excluding the first uses, where they are going to be most frequent, since my data must be populated first, but they will get less frequent as number of records grow
    - can't really say, but I think it should run on lowest possible spec with decent performance (in terms of GUI responsiveness)

    Yes, I'm aware of limitations of 16-bit identifier, but I will never need more than 50000~ records and I also planned to, after learning about flatbuffer and making it work with binary file, to also learn more about templating, so I will be trying to build it using templates to easily change identifier type to 32-bits, etc.

    So the fact I want it to be as efficient (in terms of memory / CPU) as possible is only to learn how to optimize things, build something that is not trivial and learn more about STL containers. This will also limit the possibility of having my GUI not responsive at the time of insert / removal.

    So any suggestions will be appreciated.

  6. #6
    Guest
    Guest
    Sorry for the late response, just got home. I concur with Salem about doing the interface first and then experimenting. This multi-container interaction stuff can be surprisingly bug prone.

    (…) but why is the Vector<Records> kept around
    How else can I do this without copying the data?
    Copying is only bad if it happens very frequently. Desktop computers laugh at copies, so if a copy is done to set up a the data in a more efficient way for later manipulation, it's probably worth it. But from what I understand, you want to allow the user to manipulate data of this form:
    Code:
    row | ID | Record
    ------------------
     0  | 55 | {data}
     1  | 12 | {data}
     2  | 62 | {data}
     3  | 41 | {data}
    ...and I can see why a vector or map alone cannot do that efficiently.

    As for const references, would you be so kind and give me a rough example of how would my (pseudo) code look like? So how I should declare my vector then and what do I return from my loader? Once I know this I will know how to adjust the rest (my model) to work with that.
    (const) references don't avoid copies where a pointer would cause one, I think the advice was just about style and certainty. Returning by const reference might look like this:
    Code:
    Vector<Record>* Loader::getRecords() { // By pointer
        return &Records;
    }
    
    const Vector<Record>& Loader::getRecords() const {
        return Records;
    }
    Hard to say, but I would say about 30000 to begin with with little growth, let's say up to 40000-45000 during the lifetime of this application.

    (…)

    I think few MB, the Record will only hold few strings, plenty of booleans and several integers
    If you know the relevant fields in advance, then I would suggest you simply start with a std::vector<Row> and search/sort on demand.
    Code:
    struct Row {
        uint16_t id,
        std::string name,
        bool property
    };
    
    std::vector<Row> Records;
    When the user changes the sort-by column, you could call std::sort() and pass it a custom lambda to sort by the field specified. Access by index would again become trivial.

    I think that should be more straight forward to think about than three separate containers.

  7. #7
    Guest
    Guest
    Heads up: My reply is pending, it got stuck in moderator approval state. Basically, I'd start with a vector storing the entire rows with id and other fields.

  8. #8
    Registered User
    Join Date
    Jun 2017
    Posts
    5
    Quote Originally Posted by Guest View Post
    Sorry for the late response, just got home. I concur with Salem about doing the interface first and then experimenting. This multi-container interaction stuff can be surprisingly bug prone.


    Copying is only bad if it happens very frequently. Desktop computers laugh at copies, so if a copy is done to set up a the data in a more efficient way for later manipulation, it's probably worth it. But from what I understand, you want to allow the user to manipulate data of this form:
    Code:
    row | ID | Record
    ------------------
     0  | 55 | {data}
     1  | 12 | {data}
     2  | 62 | {data}
     3  | 41 | {data}
    ...and I can see why a vector or map alone cannot do that efficiently.


    (const) references don't avoid copies where a pointer would cause one, I think the advice was just about style and certainty. Returning by const reference might look like this:
    Code:
    Vector<Record>* Loader::getRecords() { // By pointer
        return &Records;
    }
    
    const Vector<Record>& Loader::getRecords() const {
        return Records;
    }

    If you know the relevant fields in advance, then I would suggest you simply start with a std::vector<Row> and search/sort on demand.
    Code:
    struct Row {
        uint16_t id,
        std::string name,
        bool property
    };
    
    std::vector<Row> Records;
    When the user changes the sort-by column, you could call std::sort() and pass it a custom lambda to sort by the field specified. Access by index would again become trivial.

    I think that should be more straight forward to think about than three separate containers.
    I see, well, yes, you're right about the data "form" apart from the part where records are ordered by ID (and this is the only way of ordering I need, no need to order by any other properties).

    Would you please elaborate on how const reference will be better in this use-case than the pointer (cons and pros) apart from the "style" reason? I will of course make use of that advice, I'm just curious.

    As for your suggestion to use the vector alone and search on demand, this is what I've already managed to do. Didn't test it, because I still don't have enough data, but after doing some further research I came to the conclusion that my initial approach of using combination of set and set along with my initial vector seems like the best choice if I wanted to optimize it without introducing any more dependencies. So I will probably just play around with that solution to see how it works

    So there's only question of const reference vs pointer (or shared pointer?) and how these two will differ in terms of behavior.

    Other than that thank you for your help guys I will just keep experimenting to see where it takes me

  9. #9
    Guest
    Guest
    I see, well, yes, you're right about the data "form" apart from the part where records are ordered by ID (and this is the only way of ordering I need, no need to order by any other properties)
    Ah I see, I had imagined a typical table layout where users may sort by column asc/desc

    Would you please elaborate on how const reference will be better in this use-case than the pointer (cons and pros) apart from the "style" reason? I will of course make use of that advice, I'm just curious.
    (…)
    So there's only question of const reference vs pointer (or shared pointer?) and how these two will differ in terms of behavior.
    Style is basically the main benefit. You don't need dereference syntax like *ptr and ptr->member on references. The const part is unrelated.
    Code:
    Record* recPtr = &some_record; // pointer to record
    Record& recRef = some_record; // reference to record
    
    const Record* recPtr = … // pointer to const record
    const Record& recRef = … // const reference to record
    Since a reference cannot be rebound, nor point to NULL/nullptr, there's no reference equivalent to const Record* const. You may need rebinding functionality for some programs, so pointers haven't been obsoleted by references.

    References have some other roles in modern C++ but that's beyond the topic. I'd prefer them where possible because they're cleaner and leave less room for error.

    As for your suggestion to use the vector alone and search on demand, this is what I've already managed to do. (…) my initial approach of using combination of set and set along with my initial vector seems like the best choice if I wanted to optimize it without introducing any more dependencies. So I will probably just play around with that solution to see how it works
    Yes, your original idea might be best suited. Try to benchmark it if you can, I suspect either is really fast on a dataset this size.
    Last edited by Guest; 06-25-2017 at 09:41 AM.

  10. #10
    Lurking whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    9,612
    So there's only question of const reference vs pointer (or shared pointer?) and how these two will differ in terms of behavior.
    There is a difference. Pointers can be assigned and reassigned, and references can't. Pointers can also be null, and references can't. Pointers are often a symbol of needing dynamic memory, because they can access memory from the heap, instead of memory that falls out of scope like normal. If you need any of these features, then the choice is easy. You have to use pointers. If you don't need those features, then use references. The difference between them might seem small, but in C++, references are much, much more common in code.

    I think most people would tell you to use a reference here because vector manages its own memory, and you should treat it like any other automatic variable.

    I also have something to say about Adrian's post.
    Quote Originally Posted by Guest View Post
    Copying is only bad if it happens very frequently. Desktop computers laugh at copies, so if a copy is done to set up a the data in a more efficient way for later manipulation, it's probably worth it. But from what I understand, you want to allow the user to manipulate data of this form:
    Code:
    row | ID | Record
    ------------------
     0  | 55 | {data}
     1  | 12 | {data}
     2  | 62 | {data}
     3  | 41 | {data}
    ...and I can see why a vector or map alone cannot do that efficiently.
    Actually, I think vector<pair<int, Record>> makes significant strides in accessing both efficiently. Perhaps build the vector like so:
    Code:
    vector<pair<int, Record>> records(4);
    records[0] = make_pair(55, {data});
    records[1] = make_pair(12, {data});
    records[2] = make_pair(62, {data});
    records[3] = make_pair(41, {data});
    Now, the records are sorted by row. My guess is though that the sort by row is significantly less helpful than the sort by id, but still I think a vector of pairs is just an option that people don't consider often. (You could just make an array of IDs - 55, 12, 62, 41 - and swap places according to that if you are worried about getting back to row-order.)

  11. #11
    Registered User
    Join Date
    Jun 2017
    Posts
    5
    Thank you all for your help. I decided to follow some of your advices and try to use a vector only for now, before I make it work the way I expect.

    Now, I tried to use const reference, but I was unable to do this, because I would have to pass it during model's initialization and this is just not going to happen (since my model instance is created before the database / file is loaded). I decided not to play around with so called "naked" pointer and instead used a std::shared_ptr.

    I thought I'd share my code here (at least what I have so far).

    main.cpp
    Code:
    #include "mainwindow.h"
    #include <QApplication>
    
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        MainWindow w;
        w.show();
    
    
        return a.exec();
    }
    mainwindow.h:
    Code:
    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    
    #include "testitemlistmodel.h"
    
    
    #include <QDataWidgetMapper>
    #include <QMainWindow>
    #include <QStandardItem>
    
    
    namespace Ui {
        class MainWindow;
    }
    
    
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    
    
    public:
        explicit MainWindow(QWidget *parent = 0);
        ~MainWindow();
    
    
    private:
        TestItemListModel *model;
        QDataWidgetMapper *mapper;
        Ui::MainWindow *ui;
    };
    
    
    #endif // MAINWINDOW_H
    mainwindow.cpp
    Code:
    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    #include "testclass.h"
    
    
    MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::MainWindow)
    {
            // removed irrelevant code
    
    
        model = new TestItemListModel();
        std::vector<TestClass> data;
    
    
        for(uint8_t i = 1; i < 11; i++) {
            TestClass item;
            item.setName("Name " + std::to_string(i));
            item.setDescription("Description " + std::to_string(i));
            item.setIdentifier(i);
    
    
            data.push_back(item);
        }
    
    
        model->initializeData(std::make_shared< std::vector<TestClass> >(data));
    
    
        ui->listView->setModel(model);
    }
    
    
    MainWindow::~MainWindow()
    {
        delete ui;
    }
    testclass.h (header only):
    Code:
    #ifndef TESTCLASS_H
    #define TESTCLASS_H
    
    
    
    
    class TestClass
    {
    private:
        uint16_t identifier;
        std::string name;
        std::string description;
    
    
    public:
        // setters
        void setIdentifier(uint16_t identifier) { this->identifier = identifier; }
        void setName(std::string name) {this->name = name; }
        void setDescription(std::string description) {this->description = description; }
    
    
        // getters
        uint16_t getIdentifier() { return identifier; }
        std::string getName() { return name; }
        std::string getDescription() { return description; }
    };
    
    
    #endif // TESTCLASS_H
    testitemlistmodel.h:
    Code:
    #ifndef TestITEMLISTMODEL_H
    #define TestITEMLISTMODEL_H
    
    
    #include <QAbstractListModel>
    #include <testclass.h>
    #include <memory>
    
    
    class TestItemListModel : public QAbstractListModel
    {
        Q_OBJECT
    
    
        std::shared_ptr<std::vector<TestClass>> myData;
        enum : uint8_t {
            IDENTIFIER = 0x00,
            NAME = 0x01,
            DESCRIPTION = 0x02,
            FIRST = IDENTIFIER,
            LAST = DESCRIPTION
        };
    
    
    public:
        explicit TestItemListModel(QObject *parent = 0);
    
    
        QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
        bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
        void initializeData(std::shared_ptr<std::vector<TestClass>> data);
    };
    
    
    #endif // TestITEMLISTMODEL_H
    testitemlistmodel.cpp:
    Code:
    #include "testitemlistmodel.h"
    
    
    TestItemListModel::TestItemListModel(QObject *parent)
        : QAbstractListModel(parent)
    {
    }
    
    
    void TestItemListModel::initializeData(std::shared_ptr<std::vector<TestClass>> data)
    {
        if(nullptr != data) {
            this->myData = data;
        }
    }
    
    
    QVariant TestItemListModel::data(const QModelIndex &index, int role) const
    {
        if (!index.isValid())
            return QVariant();
    
    
        int row = index.row();
        if(myData->size() < row || 0 > row) {
            return QVariant();
        }
    
    
        int column = index.column();
        if((Qt::DisplayRole == role || Qt::EditRole == role) && LAST >= column && FIRST <= column) {
            switch(index.column()) {
                case IDENTIFIER:
                {
                    return QVariant((*myData)[row].getIdentifier());
                }
                case NAME:
                {
                    return QVariant(QString::fromStdString((*myData)[row].getName()));
                    //return QVariant::fromValue<std::string>();;
                }
                case DESCRIPTION:
                {
                    return QVariant(QString::fromStdString((*myData)[row].getDescription()));
                }
            }
        }
    
    
        return QVariant();
    }
    
    
    bool TestItemListModel::setData(const QModelIndex &index, const QVariant &value, int role)
    {
        if (data(index, role) == value) {
            return false;
        }
    
    
        int row = index.row();
        if(!index.isValid() || Qt::EditRole != role || myData->size() < row || 0 > row) {
            return false;
        }
    
    
        int column = index.column();
        switch(column) {
            case IDENTIFIER:
                if(uint16_t val = value.value<uint16_t>()) {
                    (*myData)[row].setIdentifier(val);
                } else {
                    // throw exception ??
                }
                break;
            case NAME:
                (*myData)[row].setName(value.toString().toStdString());
                break;
            case DESCRIPTION:
                (*myData)[row].setDescription(value.toString().toStdString());
                break;
        }
    
    
        emit dataChanged(index, index);
        return true;
    }
    I did remove all code that was irrelevant here (I really only left initializing part, because this is what I focused on at this moment). I'm also aware of few things missing here (despite using shared_ptr I probably should check for nullptr anyway before accessing it). It does what it is expected to do, I did only use mockup data here to test it.

    Anyway, the main point of me posting it here is to ask for any kind of review and comments on how I could improve this code. Just to make sure it's clear, normally the data will be loaded inside MainWindow function that will create instance of my Loader, load data, extract the pointer (or reference or whatever will be suitable) and assign it to the model using TestItemListModel::initializeData function. So the model will exist by then.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 2
    Last Post: 08-28-2016, 02:24 PM
  2. How to write data from file to structure, This case is different.
    By TanzeelurRehman in forum C Programming
    Replies: 6
    Last Post: 12-22-2010, 11:34 PM
  3. Choice of data structure for storing an image
    By lilrayray in forum C Programming
    Replies: 5
    Last Post: 01-24-2010, 03:11 PM
  4. Use of #ifdef for portability - a specific case
    By hzmonte in forum C Programming
    Replies: 7
    Last Post: 11-03-2005, 11:24 PM
  5. How do I check for a specific missing structure member?
    By Unregistered in forum C Programming
    Replies: 3
    Last Post: 03-13-2002, 02:46 PM

Tags for this Thread