You need to remember that C++ is a multi-paradigmatic (OOP, functional, procedural, ..) language and you should use the programming paradigm that best solves your current issue.
OOP doesn't lend itself well to this problem. In OOP you think about single objects in isolation (concept of "a tile"). But most of your algorithms will operate on a whole collection of tiles, not a single tile.
- Find a walkable path from A to B
- Render the whole map
- Load a whole map (not load one tile N times)
- Save the whole map (not save one tile N times)
- Turn all water tiles in a radius of R into ice tiles (some magic spell for example)
- Move all actors along the map while resolving collisions
Just turning a single water tile into ice for example is a special case then. The other way around, using a function that sets a single water tile to an ice tile does not scale well at all however.
We can assume for example that most tiles on the map will not have some special script to execute (e.g. trap tiles). If you think about a single tile in isolation, then create a 2D array of tiles, every tile will have this functionality.
If you think about a whole collection of tiles instead, you can have some separate container that has the scripts to execute only for tiles that actually do require this functionality (which will probably be few in comparison).
This saves memory and performance (glampert mentioned better use of the CPU cache). It also makes your algorithms simpler and gives other advantages such as much easier and more efficient multi-threading.
OOP has its place, but in this case even people who use OOP as a silver bullet will tell you to scale it down (advice such as composition over inheritance, have a Map class but no Tile class etc).