Your Thoughts on My Library

Pages: 12
I've been working on a little functional library. It's really small. I've focused on keeping it simple.

I plan on showing some of the features and hopefully we can discuss them. I'll explain as I reveal the features.





create function: #NOTE: The create function immediately destroys the object after its ctor has run.

1. Create a class object, using a separate thread:
1
2
3
4
5
6
class Declared;
int main()
{
    cpm::Vault::create<Declared>();
    return 0;
}


2. Create a class object, separate thread, constructor calls:
1
2
3
4
5
6
7
class Declared;
int main()
{
    // Any constructor that Declared has, can be called.
    cpm::Vault::create<Declared>('c', "++", 11);
    return 0;
}


3. Create a class object, separate thread, heap or stack allocated:
1
2
3
4
5
6
7
class Declared;
int main()
{
    // Class located on the new thread's stack:
    cpm::Vault::create<Declared, cpm::stack>();
    return 0;
}


4. Create a class object, separate thread, wait for it to finish
1
2
3
4
5
6
7
class Declared;
int main()
{
    // Halts execution until the ctor of the class has finished.
    cpm::Vault::create<Declared, cpm::halt>();
    return 0;
}


5. Create a class object, separate thread, catch exceptions
1
2
3
4
5
6
7
class Declared;
int main()
{
    // Makes sure exceptions are caught.
    cpm::Vault::create<Declared, cpm::toss>();
    return 0;
}


6. Create a class object, separate thread, combine flags
1
2
3
4
5
6
7
class Declared;
int main()
{
    // skip, opposite of halt. heap, opposite of stack. drop, won't catch exceptions, opposite of toss.
    cpm::Vault::create<Declared, cpm::skip | cpm::heap | cpm::drop>();
    return 0;
}





That's all there is to the create function. Let's see what can happen in the ctor of Declared:

7. All objects built by create, need to inherit from cpm::Vault. They now have access to the Vault_ptr.
1
2
3
4
5
class Declared;
Declared::Declared()
{
    Vault_ptr<int> ptr(new int(11)); // Deleted at end of scope, it's a smart ptr.
}


8. The object also gets a unique hash table
1
2
3
4
5
6
class Declared;
Declared::Declared()
{
    Vault_ptr<int> ptr(new int(11));
    store(ptr, "here"); // The pointer is stored here.
} // smart ptr NOT destroyed! 


9. The object can fetch pointers as well
1
2
3
4
5
6
class Declared;
Declared::Declared()
{
    Vault_ptr<int> ptr;
    fetch(ptr, "here"); // The pointer is fetched from "here".
} // smart ptr NOT destroyed! 


10. It can also "obtain" pointers
1
2
3
4
5
6
class Declared;
Declared::Declared()
{
    Vault_ptr<int> ptr;
    obtain(ptr, "here"); // Hash table "here" is erased
} // delete smart ptr. 


11. Manual deletion of hash table stored pointers
1
2
3
4
5
6
7
class Declared;
Declared::Declared()
{
    Vault_ptr<int> ptr(new int(11));
    store(ptr, "here"); // The pointer is stored here.
    destroy<int>("here");
} // smart ptr NOT destroyed! That'd cause double-free 


12. Automated deletion of hash table contents
1
2
3
4
5
6
7
8
9
class Declared;
Declared::Declared()
{
    Vault_ptr<int> ptr(new int(11));
    store(ptr, "here"); // When the hash table goes out of scope, the integer is deleted automagically.
} // No smart pointer delete.
Declared::~Declared()
{
} // hash table goes out of scope hereafter. 





Now you may be wondering:"What's the hash table used for anyway?". To answer this we look at the "build" function.




13. Propagation of the hash table.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Declared;
class Another;
Declared::Declared()
{
    Vault_ptr<int> ptr(new int(11));
    store(ptr, "here"); // The pointer is stored in "here".
    build<Another>(); // Just like create, but shares the hash table instead of creating a new one.
    // flags cpm::drop (no exception catching), cpm::toss (for exceptions), cpm::stack (stack allocation), cpm::heap (new the object).
} // smart ptr NOT destroyed!

Another::Another()
{
    Vault_ptr<int> ptr(obtain("here"));
} // smart pointer destroys the integer 






Now 2 objects have the same hash table, and they can access them independently of one another. They can share data.
When both objects finish running, a reference counter ticks to 0. When that happens, the hash table and all of its content is deleted safely and successfully.


14. Everything is array friendly:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Declared;
class Another;
Declared::Declared()
{
    Vault_ptr<int[]> ptr(new int[32]);
    store(ptr, "here"); // The pointer is stored in "here".
    build<Another>(); // Just like create, but shares the hash table instead of creating a new one.
    // flags cpm::drop (no exception catching), cpm::toss (for exceptions), cpm::stack (stack allocation), cpm::heap (new the object).
} // smart ptr NOT destroyed!

Another::Another()
{
    Vault_ptr<int[]> ptr(fetch("here"));
} // smart pointer doesn't destroy. 





When both objects are out of scope (destroyed), the hash table gets destroyed. The array will be correctly deallocated.




15. Manual deletion of hash table arrays
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Declared;
class Another;
Declared::Declared()
{
    Vault_ptr<int[]> ptr(new int[32]);
    store(ptr, "here"); // The pointer is stored in "here".
    build<Another>();
} // smart ptr NOT destroyed!

Another::Another()
{
    Vault_ptr<int[]> ptr(fetch("here"));
    destroy<int[0]>("here"); // ptr now becomes an invalid pointer. The hash table is now cleaned from "here".
    // Memory is now freed.
} // smart pointer doesn't destroy. 


16. Super-safe
1
2
3
4
5
6
7
8
class Declared;
Declared::Declared()
{
    Vault_ptr<int[]> ptr(new int[32]);
    store(ptr, "here");
    obtain(ptr, "here");
    destroy<int[0]>("here"); // Deletes a nullptr, safe.
} // smart ptr destroyed! 


17. Creator Control
1
2
3
4
5
6
7
8
9
10
class Declared;
Declared::Declared()
{
    fall(); // Allows thread from main() to continue, because it used the "halt" flag.
}
int main()
{
    cpm::Vault::create<Declared, cpm::halt>();
    return 0;
}


18. Creator Safety
1
2
3
4
5
6
7
8
9
class Declared;
Declared::Declared()
{
} // Allows main() thread to continue, when the hash_table goes out of scope.
int main()
{
    cpm::Vault::create<Declared, cpm::halt>();
    return 0;
}


19. Exception friendlyness
1
2
3
4
5
6
7
8
9
10
class Declared;
Declared::Declared()
{
    throw std::string().at(10); // throws, hash table deallocated, main() thread unpaused.
}
int main()
{
    cpm::Vault::create<Declared, cpm::halt | cpm::toss>();
    return 0;
}


20. Vault_ptr operators
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Declared;
Declared::Declared()
{
    Vault_ptr<int> ptr0(new int);
    Vault_ptr<int> ptr1(fetch("here"));
    Vault_ptr<int> ptr2(obtain("here"));
    Vault_ptr<int> ptr3(find("here"));
    Vault_ptr<int[]> ptr4(new int[2]);
    Vault_ptr<std::vector<int>> ptr5(new std::vector<int>);

    *ptr0; // Direct member access
    ptr0 = new int;

    ptr1 = fetch("here");
    fetch(ptr1, "here");

    ptr2 = obtain("here");
    obtain(ptr2, "here");

    ptr3 = find("here");

    ptr4[1]; // Element access

    ptr5->push_back(0);
}
int main()
{
    cpm::Vault::create<Declared, cpm::halt | cpm::drop>();
    return 0;
}


21. Vault_ptr special function
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Declared;
Declared::Declared()
{
    Vault_ptr<int> ptr(new int); // delete at end of scope.
    build<Declared>(ptr.transfer());
    
    // ptr can still be used, but it will not delete at end of scope
    (*ptr)++;
}
Declared::Declared(int *a)
{
    Vault_ptr<int> ptr(a); // Deletes a at end of scope.
    store(ptr, "here"); // Won't delete until reference counter ticks 0
}
int main()
{
    cpm::Vault::create<Declared, cpm::halt>();
    return 0;
}






So that's basically it. It allows one to create endlessly many "chains" of hash tables with the 'create' function.
Each chain can have endlessly many objects tied to it, all knowing the same hash table.

It's like a localized global storage system, which is dynamic, and above all, safe.

The items in the hash table are correctly deleted when the chain dies. Everything is array friendly as well.




I'd like to hear your thoughts or opinions on this little functional library. :)
I reserve the right to make edits to this post as I read the above post.

None of your examples can work because you're dealing with an incomplete type "Declared".

Why are you creating the objects in separate threads? Why purpose would this serve? It would certainly be very very slow.

Also, just running the constructor? That makes no sense, why not allow passing a std::function?
Last edited on
It's just to show that Declared is a class, just a prototype.

Edit:
You can pass an std::function object to the class ctor just fine.
Last edited on
It is a compiler error to work with incomplete types like that.
I know it's an error, but that was not my point. But you're right, I should've just written #include "Declared.hpp" instead. Oh well.

I create the objects in separate threads to allow endless builds that prevent the call stack from overflowing:

Declared creates Anything
Declared is destroyed
Anything creates Declared
Anything is destroyed...


And yes, it's just running the constructor because that's where I plan to put most of the execution code, altho I've been thinking of creating flags that return the objects, but critical questions come into play:

What if an active (running object) instance stores another object (that's not running) in the hash table?
> Running object quits
> Reference counter now 1
> Failure to deallocate the hash table due to the non-running object.

2nd try: non-running objects don't affect the reference counter
> Running object stores the non-running object into the hash table
> Running object quits
> Reference counter now 0
> Hash table start destroying entries
> Destroys the non-running object
> Non running object's destructor must be virtual to be successful
> Non-running object's destructor calls destroyer because refcount == 0
> Deletes self
> Deletes self
> Glibc detected: Double Free / Heap corruption
> Chaos
Last edited on
closed account (zb0S216C)
Your library just over-complicates a process that's intrinsically trivial to begin with. Besides, I see no obvious utilisation of threads in any of your code snippets. Any way, I'll ask it since no-one else has: what does your library do? I see multiple types, but you've not explained any of them; your comments don't help at all.

Wazzak
It passes a hash table where requested, to another instance.

The main idea is to overcome the following problem:

There exist class A, B,... Z.
A will create an object of B, B of C,...
A contains "some data".
B, C,... X do not need "some data" in A.
Z needs "some data".


Instead of passing it through all the constructors of each class, A can do the following:
1
2
3
4
A::A()
{
    store(Vault_ptr_that_A_contains, "This is for my friend, Z");
}


Then Z can do this:
1
2
3
4
Z::Z()
{
    obtain(Vault_ptr_that_Z_contains, "This is for my friend, Z");
}


When we want to edit this, we don't need to edit every single ctor call for every class in-between.

This is the main motivation, and that's what the library allows you to do easily.
What you describe is not a problem, so you have fixed what was not broken. Not only that, if this is the only motivation for your library, you have gone overkill and added in threads and other insanities when your solution could have just been for the base class to extend your class.
That's not the only motivation tho. Another thing I'm dealing with is that I localize resources to each class (no global resources), yet the classes can share resources as if they were global, if they want.

And how is it not a problem? Because nested calls like that do become a problem (tediousness) when you're forced to edit every single call like that.

Also, I don't want to create an inheritance chain; that'd mean that I can't dealloc/alloc them one-by-one, which is another thing. Here we can put different states of the program in different classes, when one state is being abolished, its object is simply deleted from memory, and the thread dies.

Personally I find it very clear and clean, considering that we totally avoid globals, yet retain the same "global" scope which is local to the chain.

Edit:
Btw please use examples when you state something is redundant and that there's a better/simpler way, because apparently I have been unable to see your superiour way before.
Last edited on
Bourgond Aries wrote:
Btw please use examples when you state something is redundant and that there's a better/simpler way, because apparently I have been unable to see your superiour way before.
Will do.

Bourgond Aries wrote:
I localize resources to each class (no global resources), yet the classes can share resources as if they were global, if they want.
So you have global variables that are disguised as local variables. This is even worse than plain global variables.

An example of this is static variables - they are disguised as local to the class, but really they are global.

Bourgond Aries wrote:
Because nested calls like that do become a problem (tediousness) when you're forced to edit every single call like that.
With proper OO design, I've never had to do anything tedious. And if you ever do need to edit every call like that, your IDE will generally do it for you.

In this case I would like the example from you.

Bourgond Aries wrote:
Also, I don't want to create an inheritance chain; that'd mean that I can't dealloc/alloc them one-by-one, which is another thing.
Why would you not want an inheritance chain, and why would you want to construct/destruct them separately? o_o

In this case I would also like the example from you.
L B wrote:

So you have global variables that are disguised as local variables. This is even worse than plain global variables.

An example of this is static variables - they are disguised as local to the class, but really they are global.


No, they're not actually global, sorry I don't know the precise word to describe it. The data stored in the hash table is only accessible to the chain of objects bound to that specific hash table. (I think I said global to this chain, as in accessible to the entire chain before, that's misleading of me.)

L B wrote:
With proper OO design, I've never had to do anything tedious. And if you ever do need to edit every call like that, your IDE will generally do it for you.

In this case I would like the example from you.


I think I have already given an example, alas, it failed to be a good example to you. Let me try again, but now with nested function calls:

1
2
3
4
5
6
7
8
9
void A(){int smth = 11; B(smth);}
void B(int data){/*Some other operations*/ C(data);}
void C(int data){std::cout << data;}

int main()
{
    A();
    return 0;
}


Is it really necessary that B receives that data when it doesn't at all use it? That'd be misleading when coming back to it later:
Oh hey look! B accepts an integer, it must use this to perform some dark magic!


Instead I automatically send the same hash table to such chains, so that C can simply access the data without B even having to know about it.

L B wrote:
Why would you not want an inheritance chain, and why would you want to construct/destruct them separately? o_o

In this case I would also like the example from you.


Say Class1 displays an elaborate menu system that takes a whopping 200MB of RAM. When the user "switches states", Class2 gets built. Now memory usage is 300MB!. Luckily, after Class2 has taken control over the window, Class1 will be destroyed, 200MB will be gone with it. RAM usage is now at 100MB since only Class2 is present. We do not need Class1 when we are using Class2.
Bourgond Aries wrote:
Is it really necessary that B receives that data when it doesn't at all use it? That'd be misleading when coming back to it later
It does use the data, it uses it to call C. If it does not need to call C then it does not need the data.
Bourgond Aries wrote:
Instead I automatically send the same hash table to such chains, so that C can simply access the data without B even having to know about it.
You have a potential of name conflict where a function between B and C may also use the same name for different kinds of data, and it uses the variable intended for C and possibly modifies it.

Bourgond Aries wrote:
Say Class1 displays an elaborate menu system that takes a whopping 200MB of RAM. When the user "switches states", Class2 gets built. Now memory usage is 300MB!. Luckily, after Class2 has taken control over the window, Class1 will be destroyed, 200MB will be gone with it. RAM usage is now at 100MB since only Class2 is present. We do not need Class1 when we are using Class2.
I'm not sure where this case would ever be real, and even if it was, this is a job for C++11 move constructors. Could you give a concrete example that actually happens often in real code?
L B wrote:
It does use the data, it uses it to call C. If it does not need to call C then it does not need the data.


What I meant with using the data is anything other than passing it on.

L B wrote:
You have a potential of name conflict where a function between B and C may also use the same name for different kinds of data, and it uses the variable intended for C and possibly modifies it.


I'm fully aware of that, but I reckon it's pretty easy to solve considering that we can store anything. We can store a struct containing everything a certain class needs in location "For_Class_X" or whatever. That'll be pretty hard to overwrite.

Another thing is that there is a find function, so that even in large programs you may end up with thousands of entries, you can use find and throw a message that such entry already exists when trying to store(ptr, "something");.

Thinking of it, might not be a bad idea to implement a safe_store function or something, that throws if the entry already existed.

I'm not sure where this case would ever be real, and even if it was, this is a job for C++11 move constructors. Could you give a concrete example that actually happens often in real code?


How would you use the C++11 move constructors? I've seen them being used when working with std::unique_ptrs, but for this task? Hmmm I'll just read some about that then.
The frequency of this occurrence depends on coding style or design. Maybe my design is wrong, but I'm using a different object for every big "state" of my program, which means that each state takes up quite some RAM.
Bourgond Aries wrote:
What I meant with using the data is anything other than passing it on.
You have a very strange definition of 'use'. What if you substituted the contents of the function you were calling?

@Move constructors: move ctors are used to 'take' the memory of a dying object and re-use it for a new object, rather than copying it to a new location and destroying the old. I don't know why your brain associated "move constructor" with "std::unique_ptr" - that is the last connection I would have made ;)
Last edited on
Oh because I first met std::move() during a read-up on std::unique_ptrs where one ptr was assigned to another.

std::unique_ptr<Foo> p2(std::move(p1)); // now p2 owns Foo
- en.cppreference.com

L B wrote:
What if you substituted the contents of the function you were calling?


You mean as in passing garbage to B instead of what's supposed to go through B into C?

Then in my case, nothing, since C will receive its data from A's insertion. When C however can't find A's entry, it'll create its own standard data like so:

1
2
3
4
5
6
7
8
9
10
11
C::C()
:
ptr(find("A's entry")) // If not found, nullptr returned
{
    if (!ptr) // Operator overloaded to return the value of the ptr.
        ptr = new Entry; // Entry being some random class...
    
    /*
    Muh Code
    */
}
Last edited on
Bourgond Aries wrote:
You mean as in passing garbage to B instead of what's supposed to go through B into C?
No, as in this:
1
2
3
4
5
6
7
8
9
void A(int x)
{
    std::cout << x << std::endl;
}

void B(int y)
{
    A(y);
}
1
2
3
4
void B(int x)
{
    { std::cout << y << std::endl; }
}
Most likely that is what the compiler does to optimize the code anyway (look up "function inlining"). Your hash table prevents the compiler from properly optimizing your code.
Last edited on
That's an acceptable loss, if you're going to rapidly call small functions, you don't need to make an entire class for them and use their ctor like I do. Just create a function instead.

In my case I'm making large classes for large states which are called on a non-speedy-interval.
Compiler optimizations make all the difference, if you want speed I do not understand why you are using a hash table instead of the function stack.
Because the function stack will overflow over prolonged periods.
That's like saying your executable file is too big to fit on your hard drive. That doesn't happen unless you're doing something wrong.

Unless you have some sort of infinite recursion, the stack should almost never overflow, even through the program running for centuries nonstop.
Last edited on
Pages: 12