Pointers to polymorphic classes saved in vector that reference data in a list as source
Avoid using namespace std

While it is nice to save some typing, using namespace std should be avoided. See this post for more details: https://stackoverflow.com/questions/1452721/why-is-using-namespace-std-considered-bad-practice

Have class Scene take ownership of the list

At the moment, the list data lives outside class Scene. This means other code can modify the list without Scene knowing about it. If you only want to allow items being added via Scene's add() and del() member functions, then make the list a private member variable. But in fact you already have a copy of the list in Scene, named _items. So make that the sole container of strings.

If you really need to have a std::vector&lt;shared_ptr&lt;Item&gt;&gt;, and a std::list&lt;std::string&gt;, then maybe they can be both member variables of class Scene?

Add virtual destructors to polymorphic classes

Whenever you inherit from a base class, you want the right destructor to be called. Therefore, especially if you have any non-trivial member variables, you need to declare a virtual destructor, even if it's empty:

class Item {
    public:
    Item() {}
    virtual ~Item() {}
    ...
};

class String: public Item {
    public:
    String(...) {...}
    virtual ~String() override {}
};


Consider:

Item *item = new String("foo");
delete item;


Without the virtual destructor, the base classes destructor would be called, which doesn't clean up String's member variable _s.

Consider making some member variables private

If a member variable should only ever be modified via a member function, then you should make that variable private. If other code needs to access it anyway, but only for reading, then add a function to get a const pointer or reference to the data. For example, in Scene, you don't want something to remove items directly from _items, because it would bypass the removal of strings from data. So make it private and add const access to it:

class Scene {
    std::vector&lt;std::shared_ptr&lt;Item&gt;&gt; _items;

public:
    ...
    const std::vector&lt;std::shared_ptr&lt;Item&gt;&gt; &amp;get_items() const {
        return _items;
    }
}


Maybe the same can be applied to String as well.

Avoid reinventing the wheel

Perhaps there is a reason for it, but if I just look at the code you posted, I wonder why you create your own class String. If Scene is just a container for strings, then why not have it have std::vector&lt;std::string&gt; _items?

Use auto where appropriate

You can avoid repeating types by using auto in several places. For example, in del() you could write:

auto pa = items[n];
auto pb = dynamic_pointer_cast&lt;String&gt;(pa);


Use range-based for-loops

Whenever you are iterating over the items in a container, use range-based for loops. They are easier to write, and there is less chance of errors. For example:

for (auto item: _items) {
    item-&gt;print();
}


Consider using if-statements with initializers

C++17 made if-statements a bit more powerful. For example, especially when doing dynamic-casts, you can write shorter code:

if (auto pb = dynamic_pointer_cast&lt;String&gt;(pa); pb) {
    data.remove(pb-&gt;ret());
}


Prevent out-of-bounds access

In Scene::del(), you don't check whether the supplied parameter n is smaller than the size of _items. If this function is called with a too large value, this will result in an out-of-bounds access, which, in the best case, causes a segmentation fault, but in the worst case results in weird behavior that is hard to debug.

If it's something that could happen due to wrong input to the program, you should add a run-time check, and for example throw an exception if n is too large. If the only time n would be too large is due to a programming error somewhere else in the code, then consider adding an assert() statement.

Avoid using std::endl

Use \n instead. See this post for the rationale:
https://stackoverflow.com/questions/213907/c-stdendl-vs-n

Consider overloading operator&lt;&lt;()

Instead of writing a print() function that always prints to std::cout, you could rewrite this to an overload of operator&lt;&lt;(), so that you could print your objects to any output stream. There is a slight problem when having derived classes though, the solution is to modify your print() functions to take an output stream object as a parameter, and then only overload operator&lt;&lt;() in the base class:

class Item {
public:
    ...
    virtual void print(std::ostream &amp;out) const = 0;

    friend std::ostream&amp; operator&lt;&lt;(std::ostream&amp; out, const Item &amp;item) {
        item.print(out);
    }
};

class String: public Item {
public:
    ...
    virtual void print(std::ostream &amp;out = std::cout) const override {
        out &lt;&lt; *_s &lt;&lt; '\n';
    }
};


Now you can write:

String s;
...
std::cout &lt;&lt; s;


I recommend you don't print the newline character in your print() functions though, so code that wants to print items can decide itself whether they should be on separate lines or not.

Make member functions that don't modify anything const

As I already did in the above example, functions that don't change any of the member variables should be made const. This will allow the compiler to perform more optimizations, and will cause it to generate errors when you accidentily do change something in const functions.

Another thing to consider is that if you have a const variable or const reference to a variable, then only member functions of that variable that are marked const can actually be called.

Don't move an iterator past the end

In your main(), you iterator over data. After the for loop, it is equal to data.end(). Then you write:

*it++;


Depending on the implementation of std::list&lt;&gt;, this might cause the iterator to become invalid, and either that statement itself will cause some error, or the subsequent *it = "$$$" will.