How to "create" a variable in "Runtime"?

Asked

Viewed 308 times

9

I’m doing a project that implements a Python-style console. It has several functions, it has many things. But I need to implement a command called set. set declare a character string and define its value as defined by the user.

class Variavel
{
    private:
    char* nome;
    char* valor;
    public:
    Variavel() : nome(NULL), valor(NULL)
    {
    }
    ~Variavel()
    {
        delete[] nome;
        delete[] valor;
    }
    void DefVar(const char* valor, const char* nome)
    {
        this->valor = new char[strlen(valor)];
        this->valor = const_cast<char*>(valor);
        this->nome = new char[strlen(nome)];
        this->nome = const_cast<char*>(nome);
    }
};

I could create a large array or pointer, and with each instance the client defines one more variable, the number of the array increases. When he asked for a variable, he would map each array to the end. It is a method that works, but is very slow, uses a lot of memory and is a little inefficient. How to create a Runtime variable in an efficient way?

  • 4

    Improved with the constructor and destructor, but you still have to disable the copy constructor, or implement one, otherwise you will end up with two objects pointing to the same buffers, which will generate delete's duplicated. Hence the std::string is more practical, as already commented.

  • And there is one more reason for you to use Std::string. Your Defvar function is with memory Leak. You are setting value and name without releasing what value and name already held.

3 answers

9


You can use a std::map to save your variable table. Example:

#include <map>
#include <string>
#include <iostream>

typedef std::string Variable;

std::map<std::string, Variable> table;
table["x"] = "1+1";
table["y"] = "1-1";
std::cout << table["x"] << std::endl; // 1+1

There are many classes in the standard library to do this kind of management. Note the container library.


There are several other problems in your class Variavel. First of all you keep pointers to data you have allocated (i.e., you are the owner of these resources) and as such, it is your responsibility to release them. It is necessary to define a destructor deleteI walk the pointers.

Also, there is no default constructor, so the compiler will create a blank one for you that nay initializes the pointers. Then in that case they will point to any invalid memory. You must define a default constructor (or some other). The function DefVar for example should be a builder.

In order for the object to have value semantics (can be treated as primitive types), it must also have a copy constructor, which will duplicate the allocation and a operator= which has similar operation to copy builder.

This is called: Rule of Three. (Or Rule of Five in C++11 because it can move objects).

Or easier than this: use std::string as I did in my example that already implements everything you need and has value semantics. Much simpler and more reliable than using mere pointers directly.

  • 1

    Variable class is only illustrative.

2

The most efficient container for storing many objects is the std::vector. Internally it is just an array. If you will need to do searches on it, you can make it efficient using the algorithm std::lower_bound to insert the elements in an orderly fashion into the vector, and then also to perform the search.

#include <string>
#include <vector>
#include <algorithm>

struct Variavel {
    std::string nome, valor;

    Variavel(const std::string &nome, const std::string &valor="")
      : nome(nome), valor(valor) {
    }
};

//necessário para ordenar as variáveis no vector
bool operator<(const Variavel &lhs, const Variavel &rhs) {
    return lhs.nome < rhs.nome;
}

std::vector<Variavel> variaveis;

//adiciona uma variável
void setVar(const std::string &nome, const std::string &valor) {
    Variavel v(nome, valor);
    //Posição onde se adicionar para que a lista fique em ordem
    std::vector<Variavel>::iterator pos = std::lower_bound(variaveis.begin(), variaveis.end(), v);
    variaveis.insert(pos, v);
}

//Obtem uma variável. Retorna ponteiro nulo caso nao exista
const Variavel *getVar(const std::string &nome) {
    std::vector<Variavel>::iterator pos = std::lower_bound(variaveis.begin(), variaveis.end(), Variavel(nome));
    if (pos != variaveis.end() && pos->nome == nome) {
        return &(*pos);
    }
    else {
        return 0;
    }
} 

Probably in the future you will add more content to the variable class, such as its type. If she gets too big it might be worth turning her into a wrapper to a dynamically allocated class, thereby minimizing the effect of object copies. But if you are already working with C++11 this problem is minimized.

  • Note: Insert and access elements in a std::map has logarithmic complexity (O(log n)), while the std::vector::insert is linear (O(n)) even if the search for std::lower_bound is logarithmic. Another problem is to insert two variables with the same name.

  • Yes, it will have to balance the processing x memory consumption. About the insertion of repeated elements it would be enough to check by the field name, as it was done at the time of searching the element, I did not put to not extend further the example.

1

What you want to create is a symbol table for your interpreter. At the lower level, this is a string map (variable names) for memory addresses:

std::map<std::string, void*> tabelaSimbolos;

In this way, each variable declared would be allocated in an area, with any type. To elaborate a little more (and that will probably be necessary), you will need information about this symbol. Then you will need a data structure, telling you what type of your variable is and where the data is actually. A structure similar to:

class Variavel {
  std::string tipo;
  void* dados;
};

And your map would change to:

std::map<std::string, Variavel*> tabelaSimbolos;

So, for each variable, you would access the corresponding structure, and know with which type you should interpret the data. Your map would be for example:

tabelaSimbolos["minhaString"] = {"string", 0x100000} 
tabelaSimbolos["meuInt"] = {"int", 0x2000000}

This is just a start, and very primitive. Remembering that you will almost certainly have to deal with dynamic allocations, search for RAII to avoid headaches with news and Letes.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.