pointers
I supposed the use of shared pointers was the possibility of sharing tree representation across instances, "persistence", and transactions. I’ve just watched some presentations on persistent data structures (and on Google’s trie, for that matter) so that was in my mind.
I agree with Frank about pointers. When calling code that operates on an object, it doesn’t care how the object is owned, so making it take an argument of type shared_ptr means it can’t take objects owned by unique_ptr, or that are direct members of larger structures, or on the stack, etc. So these arguments are passed as a reference.
In the Standard Guidelines, pointers are always non-owning. You mark them with owner<> to indicate otherwise.
I agree that the root does not need to be dynamically allocated. But you need to avoid having an ugly special case for the first node.
Node* n = &root;
for (auto c : key) {
int index = to_index(c);
auto& slot = n->children[index];
if (!slot) slot = make_unique<Node>();
n= slot.get();
}
duplicated traversal code
I note that both functions have the same logic to traverse the tree, in their core. Normally, like in standard containers, there will be a single function that does this, and it is used by all the other functions.
If these are the only two functions you have, it’s probably not worth the effort. But if you have more (remove, find the closest match, etc.) then you should do that.
26
The first thing I noticed on your update was that you replaced the evil macro with a magic number, rather than a better way of defining a constant.
static constexpr size_t nodesize = 1+'z'-'a';
std::unique_ptr<Node> children[nodesize];
bad key
int index = c - 'a'; // note signed result wanted
if (index<0 || index>=nodesize) throw invalid_argument("oops");
Both functions go over a string in the same manner, so make that a common function.
int index = to_index(c);
Portability of character encoding
It’s been noted that the letters are not necessarily contiguous in the source character set. However, if you are writing in (original) EBCDIC you have worse problems and would not be able to type the { }
characters into the source file. (I’ve discussed C++ on a primitive type of forum software running on an EBCDIC system that was lacking [ ]
and a few others, and it is not simple.
The execution character set is distinct from the source character set, and depends on the locale. More generally, you can see that it depends on the source of the strings such as a saved file — if the file uses a character set that doesn’t use the same codes for letters as we expected, then things will go bad.
So, part of the specification is that the input strings will always be in UTF-8, or (sufficient for our purposes) is compatible with ASCII.
What about at compile-time though? The standard says that the value of a character literal 'a'
is in the execution character set, not the source character set, which is good. Except that the execution character set is not known until run time, so how can it do that?
However, you can specify that a character is using UTF-8, regardless of any locale or whatnot going on in the compiler or target system.
static constexpr size_t nodesize = 1+u8'z'-u8'a';
shared_ptr
to the previous questions \$\endgroup\$